[Node.js] pm2 cluster 사용법

STEVELOPER·2022년 9월 29일
0

Node.js

목록 보기
4/9

서버 작업을 하다보니 "node-schedule" 모듈을 사용할 일이 생겼었다.
서버는 node.js express 를 사용하면서 apollo server 를 사용했는데 API 와 스케쥴러를 동시에 사용하고 있고, 같은 리소스를 쓰는 다른 프로젝트 또한 구동되고 있어서 4코어를 가지고 있는 cpu 임에도 불구하고 요청과 응답이 너무 느리고 이벤트루프에 스택이 쌓이는게 느껴질 정도였다.
여러번 cluster 를 사용 해보려 했으나 제대로 찾아보고 하지 않은 탓에 무작정 cluster 모듈을 사용해서 서버를 구동하려 하니 제대로 동작하지 않는 느낌이었다.
그래서 찾아보는 도중 pm2 가 cluster 를 지원하며 pm2 의 강점이라기에 pm2 의 cluster 기능을 구현해 보았다.
원래 서버를 pm2 로 구현하고 있었으나 cluster 를 사용하고 있진 않았다.

본론

우선 node.js 특징부터 알아보자면 node.js 는 싱글스레드이며, 그래서 많은 연산을 요구하는 작업에는 약하다고 한다.
node.js 를 공부하다보면 이벤트 루프라는 것이 등장하는데
node.js 는 이 이벤트 루프라는 구조를 가지고 있다.
client 에서 요청이 들어오면 우선 call stack 에 쌓이고 이를 queue 에서 처리하는 방식이다.
그래서 어쨋든 싱글이니 여럿이 처리하는 것보다 성능이 뒤처진다고 볼 수 있다.
이를 보완하는 것이 cluster 이며 쉽게 할 수 있게 해주는 모듈이 pm2 이다.

현재 상황은 node.js express 를 사용하며 모듈 형식을 따르고 있다.

pm2 를 통해서 server.js 를 실행시킨다면

pm2 start server.js

상기와 같이 실행할 것이다.
그러면 pm2 list 를 통해서 실행된 프로세스를 확인할 수 있다.
하지만 cluster 를 사용할 것이므로 root 디렉토리에
ecosystem.config.js 파일을 생성한다.

//ecosystem.config.js
module.exports = {
  apps: [{
  name: "server", //1
  script: "./server.js", //2
  instances: 0, //3
  exec_mode: "cluster" //4
  instance_var: "INSTANCE_ID", //5
  }]
}
  1. pm2 list 를 통해 출력되는 프로세스 리스트의 name 부분을 지정한다.
    프로세스의 이름이다.
  2. pm2 를 통해 서버를 구동할때 더이상 pm2 start server.js 가 아닌
    pm2 start ecosystem.config.js 로 하게 되는데, 이때 실행될 파일을 의미한다.
  3. 프로세스의 개수를 의미하며 0 일 경우 최대 수로 구동된다.
  4. 실행 모드를 의미한다.
  5. pm2 는 멀티 프로세싱시 instance 의 id 값을 제공한다.

하지만 나의 경우 프로젝트가 전부 ESM 방식으로 작성되어 단순히 실행할 경우
"import" 를 사용할 수 없다는 에러가 발생한다.
이를 처리하기 위해 몇가지 선행작업이 필요한데,
총 세가지 모듈이 추가적으로 설치되어야 한다.

npm install --save-dev @babel/register
npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime

첫번째로 @babel/register 는 앱이 구동될 때 ESM 방식을 사용할 수 있도록 해준다.
이를 선행 처리해야 하므로 file 을 하나 생성해서 해당 모듈과 server.js 파일을 import 하도록 한다.

//server-register.js
require("@babel/register");
require("./server.js");

그리고 ecosystem.config.js 파일을 하기와 같이 변경한다.

//ecosystem.config.js
module.exports = {
  apps: [{
  name: "server", //1
  script: "./server-register.js", //2
  instances: 0, //3
  exec_mode: "cluster" //4
  instance_var: "INSTANCE_ID", //5
  }]
}

이제 기본적인 준비는 완료되었다. 하기의 코드로 서버를 구동할 수 있다.

pm2 start ecosystem.config.js

하지만 실행 도중 다른 에러에 직면했다.

ReferenceError: regeneratorRuntime is not defined

프로젝트에 async/await 을 사용하는데 이를 번역하는 regeratorRuntime 가 없어서 발생하는 문제였다.

npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime

상기의 남은 두가지 모듈인데, 이 모듈들이 해당 에러를 해결할 것이다.
이미 ESM 방식으로 프로젝트를 작성했다면 babel.config.js 파일이 있을것이다.
해당 모듈을 설치 후 해당 파일에 하기와 같이 기입한다.

//babel.config.js
{
	...
	"plugins": ["@babel/plugin-transform-runtime"]
}

이후 다시 실행하니 문제없이 서버가 구동되었다.

프로세스를 나눠서 작업 할당하기

현재 프로젝트는 node-schedule 을 통해 1분 주기의 작업과 API 의 요청에 응답하는 두가지의 일을 하고 있다.
실행되고 있는 총 프로세스의 수가 4개이고 첫번째 프로세스만 sheduler 를 실행시키기 위해 작업을 나누려고 한다.
ecosystem.config.js 파일을 확인하면 instance_var 의 프로세스명을 지정할 수 있는데, 여기에서는 "INSTANCE_ID" 라고 지정했다.
기본값은 "NODE_APP_INSTANCE" 라고 한다.
이제 매 프로세스마다 "process.env.INSTANCE_ID" 를 통해 해당 프로세스의 id 를 가져올 수 있다.

process.env.INSTANCE_ID //0, 1, 2, 3

이를 통해 조건문으로 scheduler 와 API 의 작업을 분리할 수 있게되었다.

REFS
https://engineering.linecorp.com/ko/blog/pm2-nodejs
https://songjang.tistory.com/11
https://velog.io/@haebin/React-regeneratorRuntime-is-not-defined-%EC%97%90%EB%9F%AC-%ED%95%B4%EA%B2%B0
https://gareen.tistory.com/95

profile
JavaScript, Node.js, Express, React, React Native, GraphQL, Apollo, Prisma, MySQL

0개의 댓글