Node.js 멀티코어 활용: Cluster 모듈로 성능 개선하기
문제 상황
회사 API 서버가 CPU 4코어 중 1개만 사용하며 병목이 발생했다. 트래픽이 증가하면서 단일 프로세스의 한계가 명확해졌고, 수평 확장 전에 서버 리소스를 최대한 활용할 방법이 필요했다.
Cluster 모듈 도입
Node.js 내장 Cluster 모듈을 사용해 워커 프로세스를 생성했다.
const cluster = require('cluster');
const os = require('os');
const express = require('express');
if (cluster.isMaster) {
const numCPUs = os.cpus().length;
console.log(`Master ${process.pid} starting ${numCPUs} workers`);
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code, signal) => {
console.log(`Worker ${worker.process.pid} died, restarting...`);
cluster.fork();
});
} else {
const app = express();
// 기존 Express 앱 로직
app.listen(3000, () => {
console.log(`Worker ${process.pid} started`);
});
}
무중단 배포 구현
Graceful shutdown을 위해 SIGTERM 시그널 핸들링을 추가했다.
if (cluster.isMaster) {
process.on('SIGTERM', () => {
for (const id in cluster.workers) {
cluster.workers[id].send('shutdown');
}
});
} else {
process.on('message', (msg) => {
if (msg === 'shutdown') {
server.close(() => {
process.exit(0);
});
}
});
}
결과
- 동시 요청 처리량 250 req/s → 870 req/s
- CPU 사용률 25% → 90% (4코어 모두 활용)
- 배포 시 503 에러 제거
주의사항
워커 간 메모리는 공유되지 않는다. 세션 저장소는 Redis로 외부화했고, 인메모리 캐시는 각 워커마다 독립적으로 동작한다. 상태 공유가 필요하면 외부 저장소를 사용해야 한다.
PM2 도입도 고려했지만, 프로세스 관리만 필요한 상황에서 직접 구현이 더 가볍고 제어 가능했다.