불큐는 Node.js에서 여러 작업을 효율적으로 처리해 주는 도구다.
Redis를 사용해 작업들을 잘 정리하고, 동시에 여러 요청이 들어와도 한 번에 하나씩 안전하게 처리하도록 도와준다. 그래서 좌석 예매처럼 한 자원에 여러 요청이 동시에 들어올 때 문제가 생기지 않게 하고, 오류가 생기면 자동으로 다시 시도해준다.
- 작업 큐 관리 및 동시성 처리
여러 작업(예: 좌석 예매 요청)을 큐에 등록해 순차적 또는 동시에 안전하게 처리함. 경합 상황에서 안정적인 처리를 보장함. - Redis 백엔드 활용
Redis를 사용하여 작업 데이터와 상태를 빠르고 효율적으로 관리함. 분산 시스템에서도 일관성을 유지함. - NestJS에서의 원할한 통합
@nestjs/bull 모듈을 통해 Bull Queue를 모듈, 서비스, 컨트롤러, 프로세서 계층에 쉽게 적용할 수 있음. 데코레이터 기반의 구조로 코드 관리와 유지보수가 용이함. - 에러 핸들링 및 재시도 메커니즘
작업 처리 중 오류 발생 시 자동 재시도를 통해 시스템 안정성을 높임.
// src/ticket/ticket.module.ts
import { Module } from '@nestjs/common';
import { BullModule } from '@nestjs/bull';
import { TicketProcessor } from './ticket.processor';
import { TicketService } from './ticket.service';
import { TicketController } from './ticket.controller';
@Module({
imports: [
// BullModule.registerQueue: Bull 라이브러리를 사용하여 작업 큐를 설정한다
BullModule.registerQueue({
// 큐의 이름을 'ticket'으로 지정
name: 'ticket',
// 작업 큐를 저장하고 관리하기 위해 Redis 서버를 설정
redis: {
host: 'localhost',
port: 6379,
},
}),
],
providers: [TicketService, TicketProcessor],
controllers: [TicketController],
})
export class TicketModule {}
// src/ticket/ticket.service.ts
import { Injectable } from '@nestjs/common';
import { InjectQueue } from '@nestjs/bull';
import { Queue } from 'bull';
@Injectable()
export class TicketService {
constructor(@InjectQueue('ticket') private ticketQueue: Queue) {}
async reserveSeat(userId: string, seatNumber: string) {
// 좌석 예매 요청을 작업으로 추가함. this.ticketQueue.add()는 'ticket' 큐에 작업을 추가
await this.ticketQueue.add({ userId, seatNumber });
return '예매 요청이 접수되었습니다.';
}
}
// src/ticket/ticket.processor.ts
import { Processor, Process } from '@nestjs/bull';
import { Job } from 'bull';
// @Processor 데코레이터는 이 클래스가 Bull 큐에서 특정 큐('ticket')를 처리하는 프로세서임을 표시\
@Processor('ticket')
export class TicketProcessor {
@Process({ concurrency: 1 }) // concurrency: 1 설정으로 한번에 하나의 작업만 처리
async handleTicket(job: Job) { // 작업 객체를 인자로 받음
console.log('예매 처리 중:', job.data); // JOB.DATA는 작업 생성시 전달된 데이터
// 실제 좌석 예매 로직 수행
// 예: 좌석의 예약 가능 여부 확인 후 예약 처리
}
}
// src/ticket/ticket.controller.ts
import { Controller, Post, Body } from '@nestjs/common';
import { TicketService } from './ticket.service';
@Controller('ticket')
export class TicketController {
constructor(private readonly ticketService: TicketService) {}
@Post('reserve')
async reserve(@Body() body: { userId: string; seatNumber: string }) {
return await this.ticketService.reserveSeat(body.userId, body.seatNumber);
}
}
전체 구조
- 클라이언트 → Ticket Controller → Ticket Service:
클라이언트의 요청이 들어오면, 컨트롤러와 서비스를 거쳐 작업이 큐에 추가된다. - Ticket Queue (Bull) → Redis:
추가된 작업은 Bull Queue를 통해 Redis에 저장되고 관리된다. - Ticket Processor:
큐에 등록된 작업은 프로세서에 의해 순차적으로 처리되며, 이 과정에서 동시성 문제와 에러가 관리된다.
구동 순서
- 클라이언트 요청
- 클라이언트가 예매(예약) 요청을 HTTP로 보낸다.
- 예: POST /ticket/reserve 요청.
- Ticket Controller
- 컨트롤러는 클라이언트의 요청을 수신하고, 내부적으로 TicketService의 reserveSeat() 메서드를 호출한다.
- Ticket Service
- 서비스 계층은 요청 데이터를 받아, Bull Queue(여기서는 'ticket' 큐)에 작업(job)을 추가한다.
- 이 작업은 예매 요청 데이터를 담고 있으며, 실제 예매 처리는 큐에 등록된 후 진행된다.
- Bull Queue & Redis
- 작업은 Bull Queue를 통해 Redis에 저장된다.
- Redis는 작업 데이터를 빠르게 저장하고 관리하는 역할을 하며, 분산 환경에서도 안정적인 데이터 처리를 보장한다.
- Ticket Processor
- 프로세서(워커)는 등록된 작업을 모니터링하며, 작업을 하나씩 순차적으로 처리한다.
- 동시성 설정(concurrency: 1)으로 인해 한 번에 하나의 작업만 처리되어, 여러 요청이 겹치는 것을 방지한다.
- 작업 처리 중 오류가 발생하면, Bull Queue의 재시도 메커니즘이 이를 처리한다.
- 여러 사용자가 동시에 좌석 예매 요청을 보내더라도, 큐에 순차적으로 작업이 저장되어 한 번에 하나씩 안전하게 처리
- 작업 처리 도중 에러가 발생하면 자동 재시도 로직을 통해 안정적인 처리가 가능
- 시간이 오래 걸리는 작업(예: 좌석 예매 처리)을 백그라운드로 옮겨 메인 서버의 응답 속도를 개선하고, 전체 시스템 부하를 분산
- NestJS의 계층 구조(컨트롤러, 서비스, 프로세서)를 활용해, 각 역할이 명확하게 분리
>> Bull Queue를 적용하여, 예매와 같이 동시성이 중요한 사오항에서도 안정적으로 요청을 처리할 수 있고, 시스템 구조를 모듈화하여 유지보수와 확장에 용이해진다.