Usando Clusters no NestJS: Vantagens e Desvantagens
NestJS é um framework de desenvolvimento para aplicações Node.js que tem ganhado muita popularidade devido à sua robustez, escalabilidade e arquitetura modular. Ele é construído sobre o Express e/ou Fastify, e usa TypeScript como primeira classe, oferecendo uma experiência de desenvolvimento altamente estruturada. Uma das maneiras de melhorar a performance de uma aplicação NestJS é utilizando clusters, que permitem aproveitar múltiplos núcleos de CPU para distribuir o processamento.
Neste artigo, exploraremos como implementar clusters em uma aplicação NestJS e discutiremos as vantagens e desvantagens dessa abordagem.
O que é Cluster no Node.js?
No Node.js, o modelo de cluster permite criar múltiplos processos (workers) que compartilham o mesmo servidor, distribuindo as requisições entre eles. Isso permite que você tire proveito de sistemas com múltiplos núcleos de CPU, uma vez que o Node.js, por padrão, roda em apenas um thread. A implementação de clusters no NestJS é relativamente simples e pode melhorar significativamente a performance e escalabilidade da sua aplicação.
Como Implementar Clusters no NestJS?
Vamos ver um exemplo prático de como configurar o cluster em uma aplicação NestJS.
Passo 1: Configuração do ClusterService
Primeiro, vamos criar um serviço ClusterService
que gerencia o processo de inicialização do cluster.
// src/cluster.service.ts
import cluster, { Worker } from 'node:cluster';
import { availableParallelism } from 'node:os';
import { Logger } from '@nestjs/common';
const numCpus = availableParallelism();
const isPrimary =
typeof cluster?.isPrimary === 'boolean' ? cluster.isPrimary : false;
export class ClusterService {
private readonly logger = new Logger(ClusterService.name);
/**
* Inicializa a aplicação em modo cluster.
* @param callback Função assíncrona para inicializar a aplicação.
*/
static initialize(callback: () => Promise<void>) {
const service = new ClusterService();
if (isPrimary) {
service.logPrimaryProcess();
service.forkWorkers();
service.registerEventHandlers();
} else {
service.runWorker(callback);
}
}
private logPrimaryProcess() {
this.logger.log(`Primary process PID: ${process.pid}`);
this.logger.log(`Number of CPU Threads: ${numCpus}`);
}
private forkWorkers() {
for (let i = 0; i < numCpus; i++) {
cluster.fork();
}
}
private registerEventHandlers() {
cluster.on('fork', this.handleFork.bind(this));
cluster.on('exit', this.handleExit.bind(this));
cluster.on('message', this.handleMessage.bind(this));
}
private handleFork(worker: Worker) {
this.logger.log(`Worker started: PID ${worker.process.pid}`);
}
private handleExit(worker: Worker, code: number, signal: string | null) {
this.logger.warn(
`Worker PID ${worker.process.pid} exited with code ${code} and signal ${signal}. Restarting...`,
);
cluster.fork();
}
private handleMessage(worker: Worker, message: any) {
this.logger.log(
`Message from worker PID ${worker.process.pid}: ${JSON.stringify(message)}`,
);
}
private async runWorker(callback: () => Promise<void>) {
try {
await callback();
} catch (error) {
this.logger.error(`Worker PID ${process.pid} failed to start`, error);
process.exit(1);
}
}
}
Passo 2: Integração com o main.ts
Agora, no arquivo main.ts
, vamos modificar a inicialização para que a aplicação use o ClusterService
para distribuir o trabalho entre os workers.
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ClusterService } from './cluster.service';
async function bootstrap() {
await ClusterService.initialize(async () => {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
console.log(`Worker PID ${process.pid} listening on http://localhost:3000`);
});
}
bootstrap();
Passo 3: Verificando a Implementação
Ao iniciar a aplicação, você verá a seguinte saída no terminal, indicando que a aplicação está rodando em modo cluster:
Primary process PID: 12345
Number of CPU Threads: 4
Worker started: PID 12346
Worker started: PID 12347
Worker started: PID 12348
Worker started: PID 12349
Cada worker estará ouvindo na mesma porta (3000), distribuindo as requisições conforme necessário.
Vantagens do Uso de Clusters no NestJS
1. Melhoria na Performance
Clusters permitem que sua aplicação tire proveito de múltiplos núcleos de CPU, distribuindo as requisições entre os workers. Isso melhora significativamente o desempenho de aplicações de alto tráfego, tornando-a capaz de lidar com um número maior de conexões simultâneas.
2. Escalabilidade Horizontal
À medida que a carga de tráfego cresce, você pode facilmente escalar a aplicação criando mais workers. No NestJS, a configuração de clusters permite que a aplicação seja facilmente dimensionada conforme as necessidades de processamento.
3. Alta Disponibilidade
Quando um worker falha, o cluster pode reiniciá-lo automaticamente, garantindo que a aplicação continue funcionando sem interrupções. Isso melhora a resiliência da sua aplicação, mantendo-a disponível o tempo todo.
4. Fácil de Implementar
O NestJS oferece uma estrutura modular que facilita a integração com o modelo de clusters. Com algumas mudanças simples no código, você pode implementar clusters e melhorar a escalabilidade da sua aplicação sem complicação.
Desvantagens do Uso de Clusters no NestJS
1. Gerenciamento de Estado
Uma limitação do modelo de clusters é que cada worker é independente e não compartilha estado entre si. Se sua aplicação depende de dados compartilhados, como sessões de usuário, será necessário usar uma solução externa de gerenciamento de estado, como Redis, para garantir a consistência dos dados entre os workers.
2. Comunicação entre Workers
A comunicação entre workers pode ser mais complexa. Embora o Node.js forneça uma API para mensagens entre processos, a troca de dados entre workers pode adicionar overhead e complexidade ao código, especialmente em sistemas de grande escala.
3. Sobrecarga de Processos
Criar múltiplos workers consome mais recursos do sistema. Se não gerido corretamente, o número de workers pode aumentar rapidamente, o que pode gerar uma sobrecarga de processos e impactar a performance da aplicação em sistemas com recursos limitados.
4. Desafios no Gerenciamento de Erros
Embora os clusters melhorem a resiliência, o gerenciamento de erros pode ser um pouco mais complexo. Por exemplo, se um worker falha, é necessário garantir que o processo seja reiniciado corretamente, e que os dados processados não sejam corrompidos ou perdidos.
Conclusão
O uso de clusters no NestJS pode ser uma excelente maneira de melhorar a escalabilidade e a performance da sua aplicação, especialmente em sistemas de alto tráfego. Ele permite aproveitar múltiplos núcleos de CPU e aumenta a disponibilidade da aplicação. No entanto, como qualquer solução de arquitetura, ele traz desafios, como o gerenciamento de estado e a comunicação entre workers.
Se você estiver lidando com uma aplicação que exige alta performance e escalabilidade, implementar clusters no NestJS pode ser a solução ideal. Se, por outro lado, sua aplicação é pequena ou não exige alta capacidade de processamento, talvez você possa ficar com a configuração padrão do Node.js sem clusters.