<h2>O Problema de Performance no Node.js</h2>
<p>Node.js é single-threaded por padrão, o que significa que suas aplicações rodam em um único núcleo de processamento, independentemente de quantos núcleos sua máquina possua. Em um servidor com 8 núcleos, você estaria utilizando apenas 1, deixando 7 ociosos. Isso representa um desperdício enorme de recursos, especialmente em aplicações CPU-intensivas como processamento de imagens, criptografia ou cálculos complexos. A solução para este problema é justamente explorar o módulo <code>cluster</code> do Node.js ou utilizar <code>Worker Threads</code> para paralelizar o trabalho.</p>
<p>O <code>cluster</code> permite criar múltiplos processos filhos (workers), enquanto as <code>Worker Threads</code> utilizam threads dentro do mesmo processo. Ambas as abordagens têm seus casos de uso. Nesta aula, você aprenderá quando e como usar cada uma para maximizar a performance de suas aplicações.</p>
<h2>Cluster: Criando Múltiplos Processos</h2>
<h3>Como Funciona o Cluster</h3>
<p>O módulo <code>cluster</code> usa o modelo master-worker. O processo master distribui as conexões entre os workers, que são processos separados do Node.js. Cada worker pode executar código de forma independente, utilizando núcleos diferentes da CPU. Quando um worker morre, o master pode criar um novo, oferecendo alta disponibilidade.</p>
<pre><code class="language-javascript">const cluster = require('cluster');
const http = require('http');
const os = require('os');
if (cluster.isMaster) {
const numWorkers = os.cpus().length;
console.log(Master ${process.pid} iniciando ${numWorkers} workers...);
// Criar um worker para cada núcleo da CPU
for (let i = 0; i < numWorkers; i++) {
cluster.fork();
}
// Se um worker morrer, criar um novo
cluster.on('exit', (worker, code, signal) => {
console.log(Worker ${worker.process.pid} morreu);
cluster.fork();
});
} else {
// Código do worker
const server = http.createServer((req, res) => {
// Simular processamento
let sum = 0;
for (let i = 0; i < 1000000000; i++) {
sum += i;
}
res.writeHead(200);
res.end(Worker ${process.pid} respondendo\n);
});
server.listen(3000);
console.log(Worker ${process.pid} iniciado);
}</code></pre>
<p>Neste exemplo, o master cria um worker para cada núcleo disponível. Quando você acessa <code>localhost:3000</code>, diferentes workers respondem as requisições. Se um worker falhar, o master automaticamente cria um novo. Este padrão é perfeito para servidores web que precisam de alta disponibilidade.</p>
<h3>Comunicação Entre Processos</h3>
<p>Os workers podem se comunicar com o master através de mensagens. Isso é útil quando você precisa coletar estatísticas ou coordenar ações entre processos.</p>
<pre><code class="language-javascript">if (cluster.isMaster) {
const server = http.createServer((req, res) => {
// Master pode responder ou delegar
res.writeHead(200);
res.end('Master respondendo\n');
}).listen(3000);
for (let i = 0; i < 2; i++) {
const worker = cluster.fork();
worker.on('message', (msg) => {
console.log(Master recebeu: ${msg.count});
});
}
} else {
setInterval(() => {
process.send({ count: Math.random() });
}, 2000);
}</code></pre>
<h2>Worker Threads: Paralelismo Dentro de Um Processo</h2>
<h3>Quando Usar Worker Threads</h3>
<p>Diferentemente de cluster, as Worker Threads dividem memória dentro do mesmo processo, tornando a comunicação mais eficiente. São ideais para tarefas CPU-intensivas que não precisam de toda uma instância do Node.js. Se você precisa fazer criptografia pesada, transformação de imagens ou cálculos matemáticos, Worker Threads é sua melhor opção.</p>
<pre><code class="language-javascript">const { Worker } = require('worker_threads');
const path = require('path');
// Arquivo: worker.js
// console.log('Processamento no worker...');
function criarWorker() {
return new Promise((resolve, reject) => {
const worker = new Worker(path.join(__dirname, 'worker.js'));
worker.on('message', (result) => {
resolve(result);
worker.terminate();
});
worker.on('error', reject);
worker.on('exit', (code) => {
if (code !== 0) {
reject(new Error(Worker parou com código ${code}));
}
});
// Enviar dados para o worker
worker.postMessage({ number: 100000000 });
});
}
// No arquivo worker.js:
const { parentPort } = require('worker_threads');
parentPort.on('message', (data) => {
let sum = 0;
for (let i = 0; i < data.number; i++) {
sum += i;
}
parentPort.postMessage({ result: sum });
});</code></pre>
<p>Execute <code>node arquivo-principal.js</code> após criar ambos os arquivos. O worker processa o número pesado sem bloquear a thread principal.</p>
<h3>Pool de Workers</h3>
<p>Para otimizar, crie um pool reutilizável de workers em vez de criar um novo a cada tarefa:</p>
<pre><code class="language-javascript">const { Worker } = require('worker_threads');
const path = require('path');
class WorkerPool {
constructor(poolSize = 4) {
this.workers = [];
this.taskQueue = [];
for (let i = 0; i < poolSize; i++) {
const worker = new Worker(path.join(__dirname, 'worker.js'));
worker.busy = false;
this.workers.push(worker);
}
}
executeTask(data) {
return new Promise((resolve, reject) => {
const availableWorker = this.workers.find(w => !w.busy);
if (availableWorker) {
this.runTask(availableWorker, data, resolve, reject);
} else {
this.taskQueue.push({ data, resolve, reject });
}
});
}
runTask(worker, data, resolve, reject) {
worker.busy = true;
const handler = (result) => {
worker.removeListener('message', handler);
worker.removeListener('error', errorHandler);
worker.busy = false;
resolve(result);
if (this.taskQueue.length > 0) {
const { data, resolve, reject } = this.taskQueue.shift();
this.runTask(worker, data, resolve, reject);
}
};
const errorHandler = reject;
worker.once('message', handler);
worker.once('error', errorHandler);
worker.postMessage(data);
}
}
// Uso:
const pool = new WorkerPool(4);
async function processar() {
const resultado = await pool.executeTask({ number: 50000000 });
console.log(resultado);
}
processar();</code></pre>
<h2>Cluster vs Worker Threads: Escolhendo a Estratégia Certa</h2>
<p>Use <strong>Cluster</strong> quando você tiver múltiplas instâncias HTTP que compartilham a mesma porta, precisar de isolamento total entre processos ou quando a falha de um worker exigir reinicialização completa. Use <strong>Worker Threads</strong> quando o trabalho for CPU-intensivo e de curta duração, você quiser compartilhar memória eficientemente ou precisar de comunicação frequente entre tarefas paralelas.</p>
<p>Em aplicações reais, as duas abordagens podem coexistir: use cluster para distribuir requisições HTTP entre múltiplos processos e Worker Threads dentro de cada worker para tarefas pesadas. Essa combinação oferece o melhor dos dois mundos: escalabilidade horizontal via cluster e paralelismo fino via threads.</p>
<h2>Conclusão</h2>
<p>Você aprendeu que o Node.js oferece duas soluções poderosas para aproveitar múltiplos núcleos: <strong>cluster</strong> para processos independentes com auto-recuperação, e <strong>Worker Threads</strong> para paralelismo eficiente em memória. Implemente o cluster para suas APIs HTTP e considere Worker Threads para processamento pesado. A escolha certa aumentará drasticamente a performance de suas aplicações em servidores multi-core.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://nodejs.org/api/cluster.html" target="_blank" rel="noopener noreferrer">Node.js Cluster Documentation</a></li>
<li><a href="https://nodejs.org/api/worker_threads.html" target="_blank" rel="noopener noreferrer">Node.js Worker Threads Documentation</a></li>
<li><a href="https://nodejs.org/en/blog/announcements/workers-threads-move-forward" target="_blank" rel="noopener noreferrer">Using Worker Threads in Node.js - Node.js Blog</a></li>
<li><a href="https://medium.com/@ashleydw/scaling-nodejs-applications-8492ff8d33d1" target="_blank" rel="noopener noreferrer">Scaling Node.js Applications - Medium</a></li>
<li><a href="https://nodejs.org/en/docs/guides/nodejs-performance-best-practices/" target="_blank" rel="noopener noreferrer">Node.js Performance Best Practices - Official Guide</a></li>
</ul>