Node.js 교재 · 23편 · CPU 활용

cluster·worker_threads — CPU 코어 활용

Node 는 단일 스레드라는데 8코어 CPU 는 왜 샀나? 답은 두 가지 도구.

Node 다중 프로세스가 CPU 코어를 활용하는 컨셉 일러스트

8편 이벤트 루프에서 — Node 자바스크립트 코드는 단일 스레드. 그래서 8코어 CPU 가 있어도 자바스크립트 코드는 1코어만 쓴다. 나머지 7개는 놀고 있다.

해결책 두 가지 — cluster(같은 서버를 여러 프로세스로 복제), worker_threads(같은 프로세스 안에서 별도 스레드). 둘은 비슷해 보이지만 용도가 완전히 다르다. 이번 편이 그 분기점.

1. 왜 필요한가 — 단일 스레드의 한계

대부분의 웹 서버는 I/O 중심이라 8편의 이벤트 루프로 충분. 문제 시나리오 두 가지.

  • 웹 서버에 CPU 부하 — 같은 노드 인스턴스가 8 코어 중 1 코어만. CPU 7/8 가 놀고 응답 지연.
  • 이벤트 루프 블로킹 작업 — 이미지 리사이즈·암호화·CSV 파싱 등 CPU 빡센 일이 메인 스레드를 막아 그 동안 모든 요청 멈춤.

첫째는 cluster, 둘째는 worker_threads.

2. cluster — 같은 서버를 N개 복제

마스터 프로세스가 워커 프로세스를 fork. 각 워커가 같은 포트를 공유.

// server-cluster.js import cluster from 'node:cluster'; import os from 'node:os'; import { createApp } from './app.js'; if (cluster.isPrimary) { const cpus = os.cpus().length; console.log(`마스터 PID ${process.pid}, ${cpus}개 워커 생성`); for (let i = 0; i < cpus; i++) { cluster.fork(); } cluster.on('exit', (worker) => { console.log(`워커 ${worker.process.pid} 죽음, 재시작`); cluster.fork(); }); } else { const app = createApp(); app.listen(3000, () => { console.log(`워커 PID ${process.pid} 실행`); }); }

실행 후 top 으로 보면 — node 프로세스 8개가 8 코어를 다 쓴다. 같은 3000 포트를 OS 가 자동 로드밸런싱. 응답 속도가 ~8배.

실전 — 직접 cluster 쓰지 말 것cluster 모듈은 학습용. production 은 22편의 pm2 또는 Docker 가 같은 일을 더 안정적으로. PM2 한 줄 — pm2 start app.js -i max. 워커 죽으면 자동 재시작·로그 통합·다운타임 0 재배포까지. 25편에서 자세히.

3. worker_threads — 같은 프로세스 안 스레드

이미지 리사이즈처럼 시간 오래 걸리는 CPU 작업은 메인 이벤트 루프 막지 않게 스레드로.

// app.js (메인) import { Worker } from 'node:worker_threads'; app.post('/api/process-image', async (req, res) => { const worker = new Worker('./image-worker.js', { workerData: { imagePath: req.body.path }, }); worker.on('message', (result) => res.json(result)); worker.on('error', (err) => res.status(500).json({ err: err.message })); }); // image-worker.js (스레드) import { parentPort, workerData } from 'node:worker_threads'; import sharp from 'sharp'; const result = await sharp(workerData.imagePath) .resize(800) .webp() .toBuffer(); parentPort.postMessage({ size: result.length });

메인 스레드는 즉시 다음 요청 처리. 무거운 sharp 변환은 별도 스레드에서. 이벤트 루프 블로킹 0.

4. cluster vs worker_threads — 한 표

구분clusterworker_threads
단위프로세스 (OS 단위)스레드 (프로세스 내)
메모리완전 격리SharedArrayBuffer 공유 가능
시작 비용크다 (~수십 MB)작다 (~수 MB)
통신IPC (느림)postMessage (빠름)
크래시 영향한 워커만프로세스 전체
주 용도웹 서버 수평 확장CPU 빡센 작업 위임

외워두기 — cluster 는 같은 서버 복제, worker 는 무거운 일 위임. 둘을 같이 쓸 수도 있다 (cluster 워커가 worker_threads 를 또 생성).

5. 실전 권장

오늘날 권장 — 거의 둘 다 직접 안 씀 — ① 수평 확장은 PM2(25편) 또는 컨테이너 오케스트레이션(Kubernetes·ECS) 이 cluster 자리를 대체. ② CPU 빡센 작업은 BullMQ + Redis 같은 큐로 별도 워커 프로세스에 위임. 두 표준 패턴이 worker_threads 의 복잡도를 거의 가린다.

그렇다고 둘을 몰라도 되는 건 아님 — 외부 라이브러리가 내부적으로 둘을 쓴다. pino(로깅) 가 worker_threads 로 비동기 파일 쓰기, node-cron(스케줄러) 가 메인 루프 막지 않게 worker 활용. 동작 원리를 알면 디버깅이 빨라진다.

요약 — 23편 좌표

여기까지 정리. Node 단일 스레드 한계를 깨는 두 도구 — cluster(같은 서버를 N개 프로세스로 복제, 수평 확장) vs worker_threads(같은 프로세스 안 스레드, CPU 빡센 위임). 실전은 PM2·컨테이너·큐가 대체하지만 동작 원리는 알아둘 가치. 다음 편에서 보안 기초 — helmet·cors·rate-limit.

다음 편 예고 — 보안 기초

helmet·cors·rate-limit 으로 기본 방어. 24편.

© 2026 주나이테크(주) @JUNAITECH