Ferramentas & Produtividade

Escalabilidade Horizontal na Prática

7 min de leitura

Escalabilidade Horizontal na Prática

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

<h2>O que é Escalabilidade Horizontal</h2>

<p>Escalabilidade horizontal, também conhecida como &quot;scale-out&quot;, 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.</p>

<p>A diferença entre horizontal e vertical é crucial. Vertical é &quot;mais potência na mesma máquina&quot; — melhor CPU, mais RAM. Horizontal é &quot;mais máquinas fazendo o trabalho&quot;. Horizontal é preferível em sistemas modernos porque é mais econômico, oferece redundância (se um servidor cai, outros continuam) e permite crescimento ilimitado.</p>

<h2>Arquitetura Essencial para Escalar Horizontalmente</h2>

<h3>Load Balancer</h3>

<p>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.</p>

<p>Exemplo com <strong>Nginx</strong> como load balancer:</p>

<pre><code class="language-nginx">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;

}

}</code></pre>

<p>Aqui, <code>least_conn</code> 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.</p>

<h3>Cache Distribuído</h3>

<p>Cada requisição não deve refazer trabalho que já foi feito. Um cache compartilhado (Redis) entre todos os servidores evita processamento duplicado.</p>

<p>Exemplo com <strong>Redis em Node.js</strong>:</p>

<pre><code class="language-javascript">const redis = require(&#039;redis&#039;);

const client = redis.createClient({ host: &#039;192.168.1.5&#039;, 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(&#039;SELECT * FROM users WHERE id = ?&#039;, [userId]);

// Armazena no cache por 1 hora

await client.setex(user:${userId}, 3600, JSON.stringify(user));

return user;

}</code></pre>

<p>Todos os servidores consultam o mesmo Redis, garantindo consistência de dados em cache. Isso reduz drasticamente requisições ao banco de dados.</p>

<h3>Persistência de Estado Compartilhado</h3>

<p>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.</p>

<p>Exemplo com <strong>Session Store em Redis</strong>:</p>

<pre><code class="language-javascript">const session = require(&#039;express-session&#039;);

const RedisStore = require(&#039;connect-redis&#039;).default;

const redis = require(&#039;redis&#039;);

const redisClient = redis.createClient({ host: &#039;192.168.1.5&#039; });

app.use(session({

store: new RedisStore({ client: redisClient }),

secret: &#039;seu_secret_aqui&#039;,

resave: false,

saveUninitialized: false,

cookie: { secure: false, httpOnly: true, maxAge: 1000 60 60 * 24 }

}));

app.get(&#039;/dashboard&#039;, (req, res) =&gt; {

if (!req.session.userId) {

return res.status(401).send(&#039;Não autenticado&#039;);

}

res.send(Bem-vindo, usuário ${req.session.userId});

});</code></pre>

<p>Agora qualquer servidor pode servir qualquer cliente e recuperar a sessão do Redis.</p>

<h2>Padrões Avançados na Prática</h2>

<h3>Message Queues para Processamento Assíncrono</h3>

<p>Operações pesadas não devem bloquear requisições HTTP. Use filas de mensagens para processar tarefas em background.</p>

<p>Exemplo com <strong>RabbitMQ e Node.js</strong>:</p>

<pre><code class="language-javascript">const amqp = require(&#039;amqplib&#039;);

// Produtor: enfileira tarefa

async function enviarEmailEmBackground(userId, email) {

const connection = await amqp.connect(&#039;amqp://localhost&#039;);

const channel = await connection.createChannel();

await channel.assertQueue(&#039;emails&#039;);

channel.sendToQueue(&#039;emails&#039;, Buffer.from(JSON.stringify({ userId, email })));

console.log(&#039;Email enfileirado&#039;);

}

// Consumidor: processa tarefas em outro servidor

async function processarFilaDeEmails() {

const connection = await amqp.connect(&#039;amqp://localhost&#039;);

const channel = await connection.createChannel();

await channel.assertQueue(&#039;emails&#039;);

channel.consume(&#039;emails&#039;, async (msg) =&gt; {

const { userId, email } = JSON.parse(msg.content.toString());

await enviarEmail(email);

channel.ack(msg);

});

}</code></pre>

<p>Isso desacopla o servidor web do processamento pesado. Um servidor processa requisições; outro processa a fila.</p>

<h3>Database Replication e Sharding</h3>

<p>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).</p>

<p>Exemplo de <strong>leitura em réplica</strong>:</p>

<pre><code class="language-javascript">const masterDb = mysql.createConnection({ host: &#039;db-master.prod.com&#039; });

const replicaDb = mysql.createConnection({ host: &#039;db-replica.prod.com&#039; });

async function getUser(userId) {

// Leitura vem da réplica (mais rápido)

return await replicaDb.query(&#039;SELECT * FROM users WHERE id = ?&#039;, [userId]);

}

async function updateUser(userId, data) {

// Escrita vai para master (única fonte verdade)

return await masterDb.query(&#039;UPDATE users SET ? WHERE id = ?&#039;, [data, userId]);

}</code></pre>

<h2>Monitoramento e Escalabilidade Automática</h2>

<p>Nenhum sistema horizontal funciona sem observabilidade. Você precisa saber quantos servidores estão rodando, qual é a latência, quanto de CPU está sendo usado.</p>

<p>Exemplo com <strong>Prometheus e alertas básicos</strong>:</p>

<pre><code class="language-javascript">const prometheus = require(&#039;prom-client&#039;);

const httpRequestDuration = new prometheus.Histogram({

name: &#039;http_request_duration_seconds&#039;,

help: &#039;Duração das requisições HTTP&#039;,

labelNames: [&#039;method&#039;, &#039;route&#039;, &#039;status_code&#039;],

buckets: [0.1, 0.5, 1, 2, 5]

});

app.use((req, res, next) =&gt; {

const start = Date.now();

res.on(&#039;finish&#039;, () =&gt; {

const duration = (Date.now() - start) / 1000;

httpRequestDuration

.labels(req.method, req.route?.path, res.statusCode)

.observe(duration);

});

next();

});

app.get(&#039;/metrics&#039;, async (req, res) =&gt; {

res.set(&#039;Content-Type&#039;, prometheus.register.contentType);

res.end(await prometheus.register.metrics());

});</code></pre>

<p>Com essas métricas, você configura alertas: &quot;se latência p95 &gt; 500ms, adiciona 2 servidores&quot;. Isso é <strong>auto-scaling</strong>.</p>

<h2>Conclusão</h2>

<p>Escalabilidade horizontal é essencial em qualquer sistema que pretenda crescer além de um servidor único. Os três pilares são: <strong>(1) Distribuição de carga</strong> via load balancer, <strong>(2) Estado compartilhado</strong> em cache e sessões centralizadas, e <strong>(3) Processamento desacoplado</strong> com filas e replicas. O quarto pilar frequentemente ignorado é <strong>monitoramento obsessivo</strong> — sem métricas, você está navegando no escuro.</p>

<h2>Referências</h2>

<ul>

<li><strong>Nginx Documentation</strong>: https://nginx.org/en/docs/</li>

<li><strong>Redis Official</strong>: https://redis.io/documentation</li>

<li><strong>RabbitMQ Tutorials</strong>: https://www.rabbitmq.com/getstarted.html</li>

<li><strong>Designing Data-Intensive Applications</strong> (Martin Kleppmann): https://www.oreilly.com/library/view/designing-data-intensive-applications/9781491902141/</li>

<li><strong>Prometheus Monitoring</strong>: https://prometheus.io/docs/introduction/overview/</li>

</ul>

Comentários

Mais em Ferramentas & Produtividade

Dominando Cache e Performance em Projetos Reais
Dominando Cache e Performance em Projetos Reais

Cache: Fundamentos e Estratégias Práticas Cache é um mecanismo de armazenamen...

Como Usar PHP Moderno em Produção
Como Usar PHP Moderno em Produção

Estrutura de Projeto e Padrões Modernos PHP moderno em produção começa com um...

Mensageria Assíncrona na Prática
Mensageria Assíncrona na Prática

O que é Mensageria Assíncrona e Por Que Importa Mensageria assíncrona é um pa...