Resolvendo Problemas de Performance com Redis e Bull

Rafael Avelar Campos - Sep 10 - - Dev Community

Em um ambiente de fintech, a performance do sistema é crucial para garantir que transações financeiras sejam rápidas e seguras. A capacidade de processar um grande volume de requisições simultaneamente, sem prejudicar a experiência do usuário ou sobrecarregar a infraestrutura, é um dos principais desafios. Para alcançar isso, ferramentas como Redis e Bull têm desempenhado um papel fundamental na nossa fintech.

Neste artigo, vou compartilhar como usamos Redis e Bull para resolver problemas de performance, melhorar o tempo de resposta das transações e garantir que nosso sistema financeiro continue escalável e eficiente. Também incluirei um guia prático sobre como implementar Redis e Bull no NestJS.


O Problema: Lentidão e Sobrecarga do Sistema

Com o crescimento da nossa base de usuários e o aumento no número de transações diárias, começamos a perceber uma queda na performance do sistema, especialmente durante os horários de pico. Operações como processamento de pagamentos, geração de relatórios e notificações estavam começando a ficar mais lentas, e a sobrecarga nos servidores estava se tornando uma ameaça real.

Além disso, a necessidade de processar cada transação de forma síncrona estava começando a causar gargalos, aumentando o tempo de resposta para o usuário final. Precisávamos de uma maneira de otimizar o processamento dessas tarefas para evitar a degradação do serviço.


Solução 1: Otimização com Redis

Para melhorar o desempenho e diminuir a latência nas operações de leitura e gravação, implementamos Redis como solução de cache. Redis é um banco de dados em memória que permite acessos e gravações extremamente rápidos, ideal para armazenar dados temporários ou frequentemente acessados.

Uso de Redis na Nossa Infraestrutura

  • Cache de Sessão e Autenticação: Em vez de fazer consultas constantes ao banco de dados para verificar sessões ativas, passamos a armazenar tokens de autenticação no Redis. Isso reduziu significativamente a latência nas autenticações de usuários e no processamento de transações.

  • Cache de Transações Temporárias: Durante o processamento de pagamentos, os dados da transação são armazenados temporariamente no Redis antes de serem gravados no banco de dados principal. Isso nos permitiu processar as transações de forma muito mais rápida, enquanto o sistema lida com operações críticas de forma assíncrona.

  • Redução de Consultas ao Banco de Dados: Armazenamos dados frequentemente acessados no Redis, como informações de configuração, estados de pagamentos e logs temporários. Isso reduziu a carga no banco de dados relacional e melhorou o tempo de resposta para os usuários finais.

Resultados

A implementação de Redis reduziu significativamente a latência em várias partes críticas do nosso sistema, especialmente nas operações que dependem de consultas rápidas e repetidas. A melhora foi perceptível tanto no front-end, com tempos de resposta mais rápidos, quanto no back-end, que suportava melhor o aumento de requisições.


Solução 2: Processamento Assíncrono com Bull

Mesmo com a otimização via Redis, percebemos que algumas operações precisavam ser desacopladas do fluxo principal, especialmente tarefas que demandavam mais tempo de processamento, como notificações de transações, geração de relatórios e integração com sistemas externos.

Foi aí que implementamos Bull, uma biblioteca de gerenciamento de filas baseada em Redis, que nos permitiu processar tarefas de maneira assíncrona, sem bloquear o fluxo principal de execução.

Uso de Bull para Gerenciamento de Filas

  • Processamento de Transações Assíncronas: Em vez de processar todas as transações de forma síncrona, movemos partes do processo para uma fila de tarefas com Bull. Isso permitiu que nossa aplicação continuasse respondendo rapidamente aos usuários, enquanto tarefas mais demoradas eram processadas em segundo plano.

  • Notificações em Tempo Real: Uma das primeiras aplicações de Bull foi na fila de envio de notificações. Como o envio de notificações por e-mail ou push pode ser demorado e envolvem chamadas a APIs externas, movemos esse processo para uma fila, garantindo que os usuários recebessem suas atualizações sem impactar o desempenho geral da plataforma.

  • Gerenciamento de Retries e Falhas: Bull também nos permitiu gerenciar falhas em tarefas de forma eficiente. Quando uma tarefa falhava, era reprocessada automaticamente após um tempo configurado, sem necessidade de intervenção manual, garantindo maior resiliência do sistema.

Resultados

Ao introduzir Bull, conseguimos distribuir a carga de trabalho de maneira mais eficiente e evitar o bloqueio do fluxo principal da aplicação. Isso resultou em uma melhoria significativa no tempo de resposta, mesmo em momentos de pico de uso. Além disso, conseguimos escalar de forma mais previsível, já que as tarefas eram processadas conforme a capacidade do sistema.


Implementação Prática: Integrando Redis e Bull no NestJS

Abaixo, apresento um guia passo a passo sobre como integrar Redis e Bull em uma aplicação NestJS, a partir das lições aprendidas na nossa experiência prática.

1. Instalação das Dependências

Primeiro, instale os pacotes do Bull e Redis no seu projeto NestJS:

npm install @nestjs/bull bull redis
npm install @types/redis --save-dev
Enter fullscreen mode Exit fullscreen mode

2. Configuração do Módulo Bull

Crie um módulo para configurar Bull com Redis. No queue.module.ts, registre o Bull:

import { BullModule } from '@nestjs/bull';
import { Module } from '@nestjs/common';
import { QueueProcessor } from './queue.processor';
import { QueueService } from './queue.service';

@Module({
  imports: [
    BullModule.forRoot({
      redis: {
        host: 'localhost',
        port: 6379,
      },
    }),
    BullModule.registerQueue({
      name: 'email', // Nome da fila
    }),
  ],
  providers: [QueueProcessor, QueueService],
  exports: [QueueService],
})
export class QueueModule {}
Enter fullscreen mode Exit fullscreen mode

3. Criar o Processador de Filas

Crie o queue.processor.ts para processar as tarefas na fila:

import { Processor, Process } from '@nestjs/bull';
import { Job } from 'bull';

@Processor('email') // Nome da fila que vamos processar
export class QueueProcessor {
  @Process() // Aqui lidamos com o processamento de cada job
  async handleEmailJob(job: Job) {
    console.log(`Processando job #${job.id} com os dados:`, job.data);

    // Simular envio de e-mail
    const { to, subject, text } = job.data;
    await this.sendEmail(to, subject, text);

    console.log('Email enviado com sucesso');
  }

  private async sendEmail(to: string, subject: string, text: string) {
    // Simulação de envio de e-mail (coloque sua lógica aqui)
    return new Promise((resolve) => {
      setTimeout(() => {
        console.log(`Email enviado para: ${to}, com assunto: ${subject}`);
        resolve(true);
      }, 3000);
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

4. Serviço para Adicionar Tarefas à Fila

Crie um serviço para adicionar novas tarefas na fila em queue.service.ts:

import { Injectable } from '@nestjs/common';
import { InjectQueue } from '@nestjs/bull';
import { Queue } from 'bull';

@Injectable()
export class QueueService {
  constructor(@InjectQueue('email') private emailQueue: Queue) {}

  async addEmailJob(to: string, subject: string, text: string) {
    await this.emailQueue.add({
      to,
      subject,
      text,
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

5. Usando o Serviço no Controller

Crie um controlador para expor essa funcionalidade. No email.controller.ts:

import { Controller, Post, Body } from '@nestjs/common';
import { QueueService } from './queue.service';

@Controller('email')
export class EmailController {
  constructor(private readonly queueService: QueueService) {}

  @Post('send')
  async sendEmail(
    @Body('to') to: string,
    @Body('subject') subject: string,
    @Body('text') text: string,
  ) {
    await this.queueService.addEmailJob(to, subject, text);
    return { message: 'Email adicionado à fila!' };
  }
}
Enter fullscreen mode Exit fullscreen mode

Testando a Implementação

  • Suba o Redis localmente:

Para testar, certifique-se de que o Redis esteja rodando localmente. Você pode usar o Docker para iniciar o Redis facilmente:

   docker run -p 6379:6379 redis
Enter fullscreen mode Exit fullscreen mode
  • Execute o aplicativo:

Com o Redis rodando, execute o NestJS com o comando:

   npm run start:dev
Enter fullscreen mode Exit fullscreen mode
  • Envie uma requisição:

Agora, você pode testar a API com uma ferramenta como o Postman ou cURL, enviando uma requisição POST para http://localhost:3000/email/send com o seguinte body:

   {
     "to": "usuario@example.com",
     "subject": "Teste de Email",
     "text": "Esse é um email de teste."
   }
Enter fullscreen mode Exit fullscreen mode

Se tudo estiver configurado corretamente, você verá no console logs mostrando que o job foi adicionado à fila e que o processador lidou com ele.


Benefícios Gerais das Implementação

Aqui está um exemplo prático de como integrar Redis e Bull no NestJS para gerenciar tarefas assíncronas. Vamos usar o Redis como uma base para o Bull, que será responsável por gerenciar filas de tarefas de forma eficiente.

Conclusão

Neste exemplo, mostramos como integrar Redis e Bull em um projeto NestJS para criar um sistema de filas robusto, onde tarefas demoradas são processadas de forma assíncrona. Essa abordagem é ideal para sistemas que precisam gerenciar grandes volumes de transações ou outras operações que exigem alta performance e resiliência.

. . . . . .
Terabox Video Player