일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- Dinosaur
- dfs
- MongoDB
- class
- game
- nodejs
- Queue
- jest
- react
- Nest.js
- 게임
- Express
- typeORM
- Bull
- 공룡게임
- nestjs
- MySQL
- Python
- OCR
- mongoose
- cookie
- GIT
- JavaScript
- 자료구조
- AWS
- 정렬
- flask
- Sequelize
- TypeScript
- Today
- Total
포시코딩
[NestJS] 다중 서버에서의 Bull, Event-Emitter - 해결중 본문
개요
면접 보다가 어느 개발자분께서 내 프로젝트에 대해 아주 흥미로운 질문을 한 적이 있다.
"Bull과 Event-Emitter를 통해 동시성 문제를 해결하셨다고 했는데 만약 서버가 여러 대라면 그대로 동작할까요?"
잠시 생각해보니 Job을 생성하고 대기하는 service와
Job을 감지하고 꺼내 쓰는 consumer 및 service가 각기 다른 서버에서 동작할 경우
둘 다 길을 잃어 제대로 동작하지 않겠구나란 생각을 했다.
한번도 고려해보지 않은 문제라 솔직하게 생각해보지 않았고 따로 한번 알아보겠다고 했던 기억이 난다.
면접이 끝난뒤부터 이 숙제는 계속 날 따라다녔다.
해당 문제를 어떻게 구현시킬까?
서버를 한 대 더 배치한 후 로드 밸런싱을 통해 테스트해봐야 하나?
그럼 local로 동작하는 Redis 서버부터 다시 세팅해야겠네 등등
며칠정도 머릿속에서 구상만 하다 오늘에서야
아예 연습용 프로젝트를 만들며 생각했던 고려사항들을 직접 다 구현해보기로 결정했는데
구현 도중 공식 문서를 꼼꼼히 읽다 아래 내용을 발견했다.
이벤트 생성자와 소비자가 단일 프로세스에 대해 로컬인 경우 대기열에서 발생하는 모든 이벤트는 로컬이다.
다른 프로세스에 의해 트리거된 이벤트 알림을 받으려면 전역 이벤트 등록을 통해야 한다. 이정도로 해석했는데
서버가 여러 대라 해도 Job을 생성했던 프로세스 내에서 Consumer가 동작한다는 뜻으로 받아들여졌다.
그럼 위에서 얘기한 문제가 해결된다.
이론적으론 해결됐지만, Local Event Listener 데코레이터를 호출하지 않아도
default가 local에서 작동하는지, 같이 써줘야 하는지, 쓴다면 어떤 시점의 데코레이터를 사용해야 하는지 등
직접 확인해봐야 할게 아직 많이 남아있었다.
Local Event Listener
먼저 프로젝트를 복사해 포트 번호만 다르게 하여 테스트를 진행했다.
service
import { InjectQueue } from '@nestjs/bull';
import { Injectable } from '@nestjs/common';
import { Queue } from 'bull';
@Injectable()
export class MeetupsService {
constructor(
@InjectQueue('join') private joinQueue: Queue,
) {}
async join(meetupId: number, userId: number) {
console.log('MeetupsService - join');
await this.joinQueue.add('addJoin',
{ meetupId, userId },
{ removeOnComplete: true, removeOnFail: true },
)
return
}
}
consumer
import { OnQueueActive, Process, Processor } from "@nestjs/bull";
import { Job } from "bull";
@Processor('join')
export class MeetupsConsumer {
// @OnQueueActive()
@Process('addJoin')
async addJoin(job: Job) {
console.log(job.data);
return {};
}
}
Controller -> Service -> Redis -> Consumer(!)
위 순서로 진행될 것이고 한 프로세스에서 실행된다면
console.log('MeetupsService - join');
console.log(job.data);
이 순서대로 출력될 것을 예상했지만
consumer의 동작이 두 서버에서 이루어지고 있었다.
consumer - 수정 후
@OnQueueActive()
@Process('addJoin')
async addJoin(job: Job) {
console.log(job.data);
return {};
}
이후 @OnQueueActive() 데코레이터의 주석을 풀고 진행했을 때도 처음엔 잘 분리되는줄 알았는데
같은 포트에 대해 여러 번 시도했을 때 다른 프로세스의 consumer가 job을 가져가는 현상이 발견되었다.
결국 Local Event Listener 데코레이터 호출로도 해결할 수 없다는걸 확인
Event-Emitter 적용
해결되지 않았지만 테스트를 위해 Event-Emitter도 적용해봤다.
이미지는 8080(왼쪽) 서버에 요청을 보낸 결과인데
이 순서로 나와야될게 8081 서버의 consumer에서 job을 가져가 controller에서 정상적인 결과를 가져가지 못한 모습이다.
Global Event Listener
여기서 한가지 아이디어를 떠올렸는데,
하나의 Job에 대해서 모든 consumer가 반응해서 Event-Emitter를 통해 이벤트를 발생시킨다면
클라이언트로 응답하기 위해 대기중이던 프로세스에서의 리스너는 데이터를 받아 정상적으로 응답을 전달할 것이고
그렇지 않은 다른 서버들에선 리스너가 없기 때문에 그대로 끝나게 되어 결과적으로 원하는 그림이 그려질 것 같았다.
이에 사용하는 consumer process에
@OnGlobalQueueCompleted(), @OnGlobalQueueActive() 데코레이터를 써봤는데
공식 문서에 나온대로 jobId가 아닌 여전히 job을 전달하던 부분과
global하게 job을 받아오고 있지도 않은 결과가 나왔다.
세팅을 잘못한거 같은데 이와 관련된 정보를 찾기가 좀 쉽지 않은 상태
이 부분은 아직까지 알아보고 있는 중이다.
* 위 방법이 잘못됐다 생각하거나 더 좋은 방법을 알고 계신 분이 계시다면 힌트 부탁드립니다.
Load Balancing
* 위에서 두 서버로 하는 테스트가 원하는 결과물을 낸다면 로드 밸런싱 환경도 추가하여 테스트 해볼 생각
'Node.js' 카테고리의 다른 글
Express + TypeScript + Mongoose with. pnpm (2) - MongoDB Integration (2) | 2023.07.31 |
---|---|
Express + TypeScript + Mongoose with. pnpm (1) - Start (0) | 2023.07.31 |
[Nest.js] applyDecorators - 작성중 (0) | 2023.04.06 |
[Nest.js] passport, jwt를 통한 로그인 구현(1) - passport-local (0) | 2023.04.06 |
4월3일 - Nest.js의 Class와 Factory (0) | 2023.04.04 |