Como eu escalei meu serviço SQS sem load balance
Nesta leitura eu explico o que eu fiz para processar mais de uma fila sem escalar novo servidor
Clusterização
o que é um cluster?
Cluster é um termo em inglês que significa “aglomerar” ou “aglomeração” e pode ser aplicado em vários contextos. No caso da computação, o termo define uma arquitetura de sistema capaz combinar vários computadores para trabalharem em conjunto ou pode denominar o grupo em si de computadores combinados. Cada estação é denominada “nodo” e, combinadas, formam o cluster. Em alguns casos, é possível ver referências como “supercomputadores” ou “computação em cluster” para o mesmo cenário, representando o hardware usado ou o software especialmente desenvolvido para conseguir combinar esses equipamentos. (fonte: opservices).
Qual o problema que eu tive?
Eu opero um serviço que consome mensagens de uma fila SQS padrão (não FIFO), responsável por enviar notificações via WhatsApp. À medida que a demanda aumentava, cada solicitação começou a levar entre 30 minutos e uma hora para ser processada.
Isso causou um impacto significativo, pois cada nova solicitação tinha que aguardar muito tempo até ser atendida.
Diante disso, considerei uma solução: adicionar um servidor adicional para trabalhar em paralelo com o primeiro. Essa abordagem reduziu o tempo de espera, mas os custos associados foram consideravelmente altos. Funcionou? Sim. No entanto, foi uma solução que não cabia no meu bolso.
Casos que podem precisar dessa solução:
- Sistema para disparo de e-mails, imagina o cliente enviar uma campanha de 500k emails, o próximo que criar a campanha vai ter sua campanha enviada quando ?
- Envio de SMS para confirmação de conta em sistemas de alta demanda (exemplo o Caixa Tem na covid)
Solucão
Diante disso, explorei outras alternativas disponíveis no mercado e descobri uma maneira de implementar clustering com o NestJS. Essa abordagem foi extremamente útil, e o código resultante é o seguinte:
main.ts:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ClusterService } from './cluster.service';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(8081);
}
ClusterService.clusterize(bootstrap);
cluster.service.ts
import * as cluster from 'cluster';
import * as os from 'os';
import { Injectable } from '@nestjs/common';
const numCPUs = 3 || os.cpus().length;
@Injectable()
export class ClusterService {
static clusterize(callback: Function): void {
// @ts-ignore: Ignorar erro relacionado a `isPrimary`
if (cluster.isPrimary) { // Substituindo isMaster por isPrimary
console.log(`Master server started on ${process.pid}`);
for (let i = 0; i < numCPUs; i++) {
// @ts-ignore: Ignorar erro relacionado a `isPrimary`
cluster.fork();
}
// @ts-ignore:
cluster.on('exit', (worker, code, signal) => {
console.log(`Worker ${worker.process.pid} died. Restarting`);
// @ts-ignore:
cluster.fork();
});
} else {
console.log(`Cluster server started on ${process.pid}`);
callback();
}
}
}
Com este código, consegui executar meu servidor criando três ou mais processos idênticos, cada um operando de forma independente na memória. Essencialmente, esse código permite que o projeto seja executado múltiplas vezes no mesmo servidor, sem impactar o tráfego na porta utilizada pelo projeto.
Espero que esta solução possa ser útil para você também. Boa sorte!
Agradeço por ler este artigo! Até breve.