Node.js 클러스터 모드로 멀티코어 활용하기

문제 상황

운영 중인 API 서버가 4코어 인스턴스에서 돌고 있는데, CPU 사용률이 한 코어에만 집중되는 현상을 발견했다. Node.js는 단일 스레드 기반이라 멀티코어를 제대로 활용하지 못하고 있었다.

클러스터 모드 적용

Node.js의 내장 cluster 모듈을 사용해 워커 프로세스를 생성하도록 수정했다.

const cluster = require('cluster');
const os = require('os');
const numCPUs = os.cpus().length;

if (cluster.isMaster) {
  console.log(`마스터 프로세스 ${process.pid} 시작`);
  
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }
  
  cluster.on('exit', (worker, code, signal) => {
    console.log(`워커 ${worker.process.pid} 종료`);
    cluster.fork();
  });
} else {
  require('./app');
  console.log(`워커 ${process.pid} 시작`);
}

무중단 재시작 구현

배포 시 SIGUSR2 시그널을 받으면 워커를 순차적으로 재시작하도록 처리했다.

if (cluster.isMaster) {
  let isRestarting = false;
  
  process.on('SIGUSR2', () => {
    if (isRestarting) return;
    isRestarting = true;
    
    const workers = Object.values(cluster.workers);
    
    const restartWorker = (index) => {
      if (index >= workers.length) {
        isRestarting = false;
        return;
      }
      
      const worker = workers[index];
      worker.disconnect();
      
      cluster.once('listening', () => {
        setTimeout(() => restartWorker(index + 1), 1000);
      });
    };
    
    restartWorker(0);
  });
}

결과

  • CPU 사용률이 전체 코어에 고르게 분산됨
  • 단일 워커 장애 시 자동으로 재시작되어 가용성 향상
  • 배포 시 다운타임 없이 재시작 가능

주의사항

세션 스토어는 메모리가 아닌 Redis를 사용해야 워커 간 공유가 가능하다. 로컬 메모리 캐시도 워커마다 독립적이라는 점을 고려해야 한다.