O que é Escalabilidade Horizontal
Escalabilidade horizontal, também conhecida como "scale-out", significa distribuir a carga de trabalho entre múltiplos servidores independentes ao invés de aumentar a potência de uma única máquina (scale-up). Quando você tem um sistema que recebe milhões de requisições, não consegue resolvê-lo apenas colocando um processador mais poderoso — precisa dividir o trabalho. A ideia é simples: se um servidor aguenta 1000 requisições por segundo, dois servidores combinados devem aguentar 2000. Na prática, é mais complexo, mas essa é a premissa fundamental que toda empresa de tech trabalha.
A diferença entre horizontal e vertical é crucial. Vertical é "mais potência na mesma máquina" — melhor CPU, mais RAM. Horizontal é "mais máquinas fazendo o trabalho". Horizontal é preferível em sistemas modernos porque é mais econômico, oferece redundância (se um servidor cai, outros continuam) e permite crescimento ilimitado.
Arquitetura Essencial para Escalar Horizontalmente
Load Balancer
Um load balancer distribui requisições entre múltiplos servidores. Ele é o ponto de entrada único que encaminha cada requisição para o servidor menos ocupado ou seguindo uma estratégia de distribuição.
Exemplo com Nginx como load balancer:
upstream backend {
least_conn;
server 192.168.1.10:3000;
server 192.168.1.11:3000;
server 192.168.1.12:3000;
}
server {
listen 80;
server_name api.exemplo.com;
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Aqui, least_conn garante que requisições vão para o servidor com menos conexões ativas. O load balancer verifica a saúde dos servidores periodicamente e remove aqueles offline automaticamente.
Cache Distribuído
Cada requisição não deve refazer trabalho que já foi feito. Um cache compartilhado (Redis) entre todos os servidores evita processamento duplicado.
Exemplo com Redis em Node.js:
const redis = require('redis');
const client = redis.createClient({ host: '192.168.1.5', port: 6379 });
async function getUser(userId) {
// Tenta pegar do cache
const cached = await client.get(`user:${userId}`);
if (cached) return JSON.parse(cached);
// Se não está em cache, busca do banco
const user = await db.query('SELECT * FROM users WHERE id = ?', [userId]);
// Armazena no cache por 1 hora
await client.setex(`user:${userId}`, 3600, JSON.stringify(user));
return user;
}
Todos os servidores consultam o mesmo Redis, garantindo consistência de dados em cache. Isso reduz drasticamente requisições ao banco de dados.
Persistência de Estado Compartilhado
Sessões e dados de estado não podem ficar no servidor local — quando um usuário é roteado para outro servidor, precisa continuar sua sessão.
Exemplo com Session Store em Redis:
const session = require('express-session');
const RedisStore = require('connect-redis').default;
const redis = require('redis');
const redisClient = redis.createClient({ host: '192.168.1.5' });
app.use(session({
store: new RedisStore({ client: redisClient }),
secret: 'seu_secret_aqui',
resave: false,
saveUninitialized: false,
cookie: { secure: false, httpOnly: true, maxAge: 1000 * 60 * 60 * 24 }
}));
app.get('/dashboard', (req, res) => {
if (!req.session.userId) {
return res.status(401).send('Não autenticado');
}
res.send(`Bem-vindo, usuário ${req.session.userId}`);
});
Agora qualquer servidor pode servir qualquer cliente e recuperar a sessão do Redis.
Padrões Avançados na Prática
Message Queues para Processamento Assíncrono
Operações pesadas não devem bloquear requisições HTTP. Use filas de mensagens para processar tarefas em background.
Exemplo com RabbitMQ e Node.js:
const amqp = require('amqplib');
// Produtor: enfileira tarefa
async function enviarEmailEmBackground(userId, email) {
const connection = await amqp.connect('amqp://localhost');
const channel = await connection.createChannel();
await channel.assertQueue('emails');
channel.sendToQueue('emails', Buffer.from(JSON.stringify({ userId, email })));
console.log('Email enfileirado');
}
// Consumidor: processa tarefas em outro servidor
async function processarFilaDeEmails() {
const connection = await amqp.connect('amqp://localhost');
const channel = await connection.createChannel();
await channel.assertQueue('emails');
channel.consume('emails', async (msg) => {
const { userId, email } = JSON.parse(msg.content.toString());
await enviarEmail(email);
channel.ack(msg);
});
}
Isso desacopla o servidor web do processamento pesado. Um servidor processa requisições; outro processa a fila.
Database Replication e Sharding
Quando o banco de dados fica saturado, réplica de leitura e sharding são necessários. Replicas servem leituras, escrita vai para master. Sharding divide dados por chave (ex: por região ou ID).
Exemplo de leitura em réplica:
const masterDb = mysql.createConnection({ host: 'db-master.prod.com' });
const replicaDb = mysql.createConnection({ host: 'db-replica.prod.com' });
async function getUser(userId) {
// Leitura vem da réplica (mais rápido)
return await replicaDb.query('SELECT * FROM users WHERE id = ?', [userId]);
}
async function updateUser(userId, data) {
// Escrita vai para master (única fonte verdade)
return await masterDb.query('UPDATE users SET ? WHERE id = ?', [data, userId]);
}
Monitoramento e Escalabilidade Automática
Nenhum sistema horizontal funciona sem observabilidade. Você precisa saber quantos servidores estão rodando, qual é a latência, quanto de CPU está sendo usado.
Exemplo com Prometheus e alertas básicos:
const prometheus = require('prom-client');
const httpRequestDuration = new prometheus.Histogram({
name: 'http_request_duration_seconds',
help: 'Duração das requisições HTTP',
labelNames: ['method', 'route', 'status_code'],
buckets: [0.1, 0.5, 1, 2, 5]
});
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = (Date.now() - start) / 1000;
httpRequestDuration
.labels(req.method, req.route?.path, res.statusCode)
.observe(duration);
});
next();
});
app.get('/metrics', async (req, res) => {
res.set('Content-Type', prometheus.register.contentType);
res.end(await prometheus.register.metrics());
});
Com essas métricas, você configura alertas: "se latência p95 > 500ms, adiciona 2 servidores". Isso é auto-scaling.
Conclusão
Escalabilidade horizontal é essencial em qualquer sistema que pretenda crescer além de um servidor único. Os três pilares são: (1) Distribuição de carga via load balancer, (2) Estado compartilhado em cache e sessões centralizadas, e (3) Processamento desacoplado com filas e replicas. O quarto pilar frequentemente ignorado é monitoramento obsessivo — sem métricas, você está navegando no escuro.
Referências
- Nginx Documentation: https://nginx.org/en/docs/
- Redis Official: https://redis.io/documentation
- RabbitMQ Tutorials: https://www.rabbitmq.com/getstarted.html
- Designing Data-Intensive Applications (Martin Kleppmann): https://www.oreilly.com/library/view/designing-data-intensive-applications/9781491902141/
- Prometheus Monitoring: https://prometheus.io/docs/introduction/overview/