핵심 정리
javascript
import cluster from "node:cluster";
import http from "node:http";
import { availableParallelism } from "node:os";
if (cluster.isPrimary) {
for (let i = 0; i < availableParallelism(); i++) {
cluster.fork();
}
cluster.on("exit", (worker) => {
if (!worker.exitedAfterDisconnect) cluster.fork();
});
} else {
http.createServer((req, res) => {
res.end(`worker ${process.pid}`);
}).listen(3000);
}프로세스 분산
먼저 머릿속에 넣어둘 기본형은 아래입니다.
- primary에서
cluster.fork() - worker 종료 감시:
cluster.on("exit", ...) - 의도적 종료 구분:
worker.exitedAfterDisconnect - HTTP 서버 멀티코어 확장:
cluster - CPU 계산 병렬화:
worker_threads
목적이 "HTTP 서버 수평 확장"이면 cluster
동일한 서버 프로세스를 코어 수만큼 띄워 요청을 나눠 처리하려는 목적이라면 cluster가 맞습니다. 프로세스가 분리되므로 한 worker 문제가 다른 worker로 덜 번집니다.
목적이 "한 프로세스 안 CPU 계산 병렬화"면 worker_threads
같은 앱 안에서 계산만 분리하고 싶고, 메모리 공유나 더 낮은 생성 비용이 중요하다면 worker_threads가 더 맞습니다.
운영에서는 재시작과 종료가 핵심이다
cluster 카드는 포크 문법보다 worker가 죽었을 때 어떻게 복구할지, 배포 중 어떻게 교체할지가 더 중요합니다. crash loop 감시까지 같이 떠올려야 운영 카드가 됩니다.
상태 공유는 자동이 아니라 외부화가 기본이다
프로세스가 나뉘면 메모리도 나뉩니다. 세션, 캐시, rate limit 같은 상태를 프로세스 메모리에만 두면 worker마다 따로 놀 수 있으니, 실제 운영에서는 Redis나 외부 저장소를 같이 떠올리는 편이 맞습니다.
언제 cluster를 쓸까
체크포인트
- HTTP 서버 멀티코어 확장:
cluster - CPU 계산 병렬화:
worker_threads - 메모리 공유 필요:
worker_threads - 프로세스 격리 우선:
cluster - 비정상 종료 복구:
cluster.on("exit") - 의도적 종료 구분:
worker.exitedAfterDisconnect - 프로세스 간 상태는 외부 저장소 기준으로 설계
주의할 점
cluster.fork()는 현재 엔트리 파일을 다시 실행한다. cluster.isPrimary 분기 없이 쓰면 worker까지 계속 fork해서 프로세스가 폭증한다.
참고 링크
1 sources