본문 바로가기
Nest

[Nest] Nestjs 멀티 스레드 (Worker)

by chu_dw 2024. 4. 13.

NestJs는 단일 쓰레드로 non-blocking 처리를 한다.

전에 한번 정리했던거 처럼 비동기와 non-bloking은 개념이 다르다. 하지만 자주 혼용되어 사용된다.

(결과적으로 둘다 동시에 다른 작업을 수행하는 것이기 때문에 코드에서 경계 구분이 애매해서 그렇다고 한다.)

대표적으로 우리가 비동기 함수라고 부르는 함수는 비동기 + 논블로킹 함수이다.

 

비동기 논블로킹 개념 한번 더 정리

그래서 그런지 여러 글들을 읽어도 계속 헷갈린다.

많은 글이 non-blocking = 비동기로 설명을 하고 있다.

 

또 오랜만에 보니 헷갈린다.. 잠시 정리

동기 비동기 = 출력순서 관련 / 블로킹 논블로킹 = 병렬처리 관련

일반적으로 병렬(동시에)처리하면 빨리 끝난에 먼저 출력해주기에,

동시에 처리해 놓고 빨리 끝난 애 입력순서 늦다고 기다리면 병렬처리 할 필요가 없으니 

비동기와 논블로킹이 묶이는 거 같다.

 

 

nestjs의 worker

원래 worker가 nest에서 동기 작업을 할 때 사용한다고 알고 있었다.

여기서 의문은 async/await를 사용해서 비동기 작업을 동기처럼 사용하는 거 아니야? 라고 생각했다.

여기 기본적인 async/await 코드이다.

 

이 코드가 async/await 없이 비동기로(출력순서 끝난 대로) 실행된다면

fetchData가 수행되기 전에 displayData가 실행될 수 있다.

이를 방지하기 위해 비동기 함수들에 await를 사용해 동기처럼 순서를 정해주는 것이다.

 

 

그럼 worker란? 

별도의 쓰레드로 작업을 가져와 처리하는 것이기 때문에 non-blocking 작업을 돕는 것이 된다.

node는 non-blokcing을 지원하지만 단일 스레드에서 실행되기 때문에 

cpu자원을 많이 사용하는 작업이 진행되는 경우 다른 요청 처리에 영향을 줄 수 있다.

이러한 작업을 이벤트 루트에서 처리하지 않고 worker를 사용해 별도의 스레드에서 처리하여

애플케이션의 응답성을 향상할 수 있다.

 

 

크러스터링 이란?

Node에선 woker와 같이 멀티스레드를 구현하는 기능이다.

크러스터링은 NestJs 애플리케이션을 여러 프로세스로 분산하여 멀티 스레드를 구현한다.

worker와 비교해 보면 아래와 같다.

   worker

   - cpu집약적 이거나 긴 시간 소요되는 작업 처리 시

   - 단일 머신에서 멀티 코어를 활용하여 병렬 처리할 때

   - 단일 스레드 모델 유지하면서 일부 작업만 별도 스레드로 (메인이벤트 루프 응답성 유지)

   클러스터링

   - 요청 수가 많고 확장성이 필요한 경우

   - 다중 코어 서버에서 애플리케이션 확장하고 부하 분산 위해 사용

   - 여러 프로세스 간 작업을 분산하여 성능 향상

 

 

worker 구현

worker 스레드는 메인 스레드로부터 메시지나 작업을 받아서 처리하는 방식으로 동작한다.

메인 스레드는 worker에게 작업을 할당하고, worker는 할당된 작업을 수행한 후 결과를 다시 메인스레드로 전달한다.

이러한 통신은 주로 메세지 큐나 이벤트 기반 방식으로 이뤄진다.

 

먼저 기본적인 worker 스레드를 구현해 보겠다

worker 생성 부분을 보면 __filename 으로 현재 파일 경로에 worker 스레드를 실행시키고

워커 스레드에 workerData를 전달한다. 워커 스레드는 이 데이터를 활용해 작업을 수행한다.

생성된 워커 스레드에선 worker thread가 출력되고 parrentPort를 통해 메인 스레드에 메세지를 보낸다.

 

그럼 실제 프로젝트에 적용해 보겠다.

일단 기능은 나중에 추가하고 일단 워커 스레드 부터 생성해 보겠다.

LetterService에 worker_threads를 import 해주고 검색 메서드 실행될 때 워커 스레드를 생성해 보겠다.

(기능적으로 나중에 추가하고 일단 공부 느낌으로, 나중에 파일 업로드 추가해서 구현해봐야겠다.)

위처럼 구현해 주었다. (기능은 없고 워커 생성 후 workerData만 전송)

worker는 같은 디렉토리에 workers 디렉토리 생성 후 아래처럼 만들어 줬다.

그럼 getAll 메서드를 실행했을 때 아래와 같은 결과를 얻을 수 있다.

 

 

 

실제 활용할 수 있는 worker를 만들기 전에 필요한 개념을 좀 더 정리하겠다.

worker 스레드 풀

worker의 리소스 관리 방식으로 미리 여러 개의 worker 스레드를 생성하고 이를 풀에 저장해 놓는다.

그리고 필요할 때마다 woker 스레드를 재사용한다.

worker 스레드 풀을 사용하지 않고 위 코드처럼 단순하게 사용하면 api 요청마다 새로운 워커 스레드를 생성하게 되고

잘못하면 서버 리소스 문제를 일으킬 수 있다.

 

worker 스레드 풀 구현

먼저 worker가 저장될 수 있는 리스트 pool을 만들어 준다.

그리고 worker생성 시 조금 쉽게 path도 미리 지정해준다.

기본이 되는 로직이다. (이 메서드가 스레드가 수행할 로직이다.)

getWorker부터 보면 worker가 저장된 pool에 worker가 없으면 createWorker()를 불러오고,

있다면 pool[0]에 있는 worker를 return 한다.

지정된 path로 worker를 생성해 주고 

worker.once로 exit 이벤트에 대한 이벤트 리스너를 등록한다. 이 리스너는 워커 스레드가 종료되었을 때 실행되는 리스너이다.

once는 해당 이벤트가 최초로 발생한 경우 콜백 함수를 실행하도록 하는 것이다.

마지막으로 assignWorker이다.

여기서는 워커스레드에 처리할 데이터 목록을 보내고 결과를 기다린다.

이때 메시지를 성공적으로 수신하면 message 이벤트, 실패하면 error 이벤트 발생한다.

 

하지만 위 코드는 하나의 워커 스레드를 Pool에서 사용하는 코드이다.

여러 워커 스레드를 풀에서 꺼내 쓰고 다 사용되면 다시 풀에 넣는 코드도 추가해야 한다.

해당 코드는 실제 프로젝트에 적용하며 정리해 보겠다.

 

(모든 postbox에 쓰여진 편지 중 특정 단어가 들어간 편지를 찾는 기능을 추가해보려 한다.)

(뭔가 실용성 보단 억지로 끼워 맞춘 느낌이 들긴 하지만... 맨 땅에 하는거보단 프로젝트에 붙이는 게 좋을 거 같아서..)

이렇게 해보려 했으나 위 작업은 대부분의 데ㅔ이터베이스 퀴리와 데이터 처리여서 cpu집약적 작업이 아니라

워커 스레드를 쓸만한 기능이 아니라고 한다..

 

그래서 postbox에 쓰여진 편지 단어들의 통계를 내보는 기능을 해볼까 한다.. 화이팅..!

 

 

 

 

 

 

https://inpa.tistory.com/entry/NODE-%F0%9F%93%9A-workerthreads-%EB%AA%A8%EB%93%88

 

[NODE] 📚 Worker_Threads 모듈 (멀티 쓰레드 구현)

Worker Thread 이해하기 Node.js가 시작되면, 다음이 실행됩니다. 하나의 프로세스 : 어디서든 접근 가능한 전역 객체이자 그 순간 실행되고 있는 것들의 정보를 가지고 있는 프로세스 하나의 스레드 :

inpa.tistory.com

https://velog.io/@ninthsun91/Nestjs-Worker-Thread%EC%99%80-Bull%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%9C-%ED%81%90%EC%9D%98-%EB%B3%91%EB%A0%AC-%EC%B2%98%EB%A6%AC

 

[Nestjs] Worker Thread와 Bull을 사용한 큐의 병렬 처리

어떤 리스트를 불러와 랭킹 알고리즘을 바탕으로 정렬을 할 때, 보통 이 정렬하는 로직은 동기적으로 이루어진 코드 비중이 높을 것이며 리스트의 길이가 길어질수록 cpu의 연산량도 많아진다.

velog.io

https://velog.io/@ninthsun91/Nestjs-Nestjs%EC%97%90%EC%84%9C%EC%9D%98-Worker-Thread-%EC%82%AC%EC%9A%A9

 

[Nestjs] Nestjs에서의 Worker Thread 사용

Nestjs 프로젝트에서 nest 커맨드를 이용해 프로젝트를 시작할 경우 항상 컴파일이 이루어지기 때문에 ts-node만을 사용할 때처럼 타입스크립트 워커 파일을 읽기 위한 js파일이나 allowJS 옵션에 대해

velog.io

 

'Nest' 카테고리의 다른 글

[Nest] Worker thread pool 구현  (0) 2024.04.18
[Nest] Nestjs logger와 middleware, intercepter  (0) 2024.04.16
[Nest] NestFactory 동작  (0) 2024.03.30
[Nest] passport  (0) 2024.03.04
[Nest] nest guard  (0) 2024.02.29