728x90
https://sejin-technology.tistory.com/130
앞글 보고 오도록 한다
일단 서버 시작점에서 부터 봐보자
다음처럼 준비하는데, 나는 도커 기반으로 서버를 작업했기 때문에 다음처럼 나온다.
혹시 코드필요한 사람은 아래 댓글 달아주면 링크보내준다
server.listen(port, () => {
const workerId = cluster.worker ? cluster.worker.id : 'single';
const mode = useCluster ? 'cluster' : 'single process';
console.log(`=== Worker ${workerId} Started ===`);
console.log(`Mode: ${mode}`);
console.log(`Process ID: ${process.pid}`);
console.log(`Listening on port: ${port}`);
if (!useCluster) {
console.log(`CPU cores available: ${numCPUs}`);
console.log(`CPU cores in use: 1 (single process mode)`);
}
console.log(`Swagger UI: http://localhost:${port}/docs`);
console.log(`==============================`);
});이런식으로 클러스터 모드를 끄면 CPU에 대한 Core를 하나만 사용하게 된다.
docker compose -f docker-compose.dev.yml run -e CLUSTER_MODE=false backend
=== Worker single Started ===
Mode: single process
Process ID: 18
Listening on port: 8080
CPU cores available: 12
CPU cores in use: 1 (single process mode)
Swagger UI: http://localhost:8080/docs
==============================
[Worker single] Client connected: V6GUekbZGMaxaDIXAAAC
[Worker single] Client connected: Z20HVclWwJzP9GHAAAAD멀티코어는 ?
docker compose -f docker-compose.dev.yml run -e CLUSTER_MODE=true backend
> video-creater@1.0.0 start
> node server.js
=== Master Process Started ===
Process ID: 18
CPU cores available: 12
Forking 12 workers...
==============================
Worker 2 (PID: 26) is online
Worker 3 (PID: 27) is online
Worker 4 (PID: 28) is online
Worker 1 (PID: 25) is online
Worker 5 (PID: 29) is online
Worker 8 (PID: 42) is online
Worker 9 (PID: 48) is online
Worker 10 (PID: 54) is online
Worker 7 (PID: 37) is online
Worker 6 (PID: 30) is online
Worker 12 (PID: 72) is online
Worker 11 (PID: 55) is online
=== Worker 2 Started ===
Mode: cluster
Process ID: 26
Listening on port: 8080
Swagger UI: http://localhost:8080/docs
==============================
=== Worker 4 Started ===
Mode: cluster
Process ID: 28
Listening on port: 8080
Swagger UI: http://localhost:8080/docs
==============================
=== Worker 6 Started ===
Mode: cluster
Process ID: 30
Listening on port: 8080
Swagger UI: http://localhost:8080/docs
==============================
=== Worker 1 Started ===
Mode: cluster
Process ID: 25
Listening on port: 8080
Swagger UI: http://localhost:8080/docs
==============================
=== Worker 10 Started ===
Mode: cluster
Process ID: 54
Listening on port: 8080
Swagger UI: http://localhost:8080/docs
==============================
=== Worker 3 Started ===
=== Worker 9 Started ===
Mode: cluster
Process ID: 48
Listening on port: 8080
Swagger UI: http://localhost:8080/docs
==============================
이렇게 나오게된다. 그러면 실제로 차이가 나는지 봐보자
테스트 코드 준비
- 일단 테스트용으로 부하를 줄 수 있는 코드를 작성
테스트 대상 - 파보나치 계산
// ============================================
// CPU 부하 테스트용 엔드포인트
// 싱글 vs 클러스터 성능 비교용
// ============================================
const cluster = require('cluster');
// 피보나치 계산 (CPU 집약적)
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
// 1. CPU 부하 테스트 - 피보나치
// 사용법: GET /api/test/cpu/fibonacci?n=40
router.get('/cpu/fibonacci', (req, res) => {
const n = parseInt(req.query.n) || 40;
const workerId = cluster.worker ? cluster.worker.id : 'single';
console.log(`[Worker ${workerId}] Fibonacci(${n}) 계산 시작...`);
const startTime = Date.now();
const result = fibonacci(n);
const duration = Date.now() - startTime;
console.log(`[Worker ${workerId}] Fibonacci(${n}) 완료: ${duration}ms`);
res.json({
worker: workerId,
pid: process.pid,
operation: `fibonacci(${n})`,
result: result,
duration: `${duration}ms`
});
});이러면 이제 재귀방식으로 파보나치를 계산하게 되는데
피보나치 재귀 - 빅오
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
시간 복잡도: O(2ⁿ)
왜냐면:
fib(5)
├── fib(4)
│ ├── fib(3)
│ │ ├── fib(2)
│ │ └── fib(1)
│ └── fib(2)
└── fib(3)
├── fib(2)
└── fib(1)
매번 2개씩 분기 → 지수적 증가
---
n별 예상 시간
┌─────┬──────────────────┬────────────┐
│ n │ 호출 횟수 (대략) │ 예상 시간 │
├─────┼──────────────────┼────────────┤
│ 30 │ 약 100만 │ ~10ms │
├─────┼──────────────────┼────────────┤
│ 40 │ 약 10억 │ ~1~2초 │
├─────┼──────────────────┼────────────┤
│ 45 │ 약 300억 │ ~30초 │
├─────┼──────────────────┼────────────┤
│ 50 │ 약 1조 │ ~10분 이상 │
└─────┴──────────────────┴────────────┘
--- 이렇게 준비를 했다.
당연히 하나씩만 보내면 코어 하나씩만쓰니까 차이가 없다.
그래서 여러개를 동시에보내서 평균값을 계산할 쉘스크립트를 하나 준비했다
./load-test.sh --concurrent 12
이렇게서 실행해서 12개를 동시에 날리게되니까 테스트 해보면 된다
안에 URL은 맞춰서 알아서 고쳐쓰면되고
#!/bin/bash
# ============================================
# CPU 부하 테스트 스크립트
# 싱글 vs 클러스터 성능 비교용
# ============================================
# 기본값
URL="http://localhost/api/test/cpu/fibonacci"
N=45
CONCURRENT=4
# 사용법 출력
usage() {
echo "사용법: $0 [옵션]"
echo ""
echo "옵션:"
echo " -c, --concurrent NUM 동시 요청 수 (기본: 4)"
echo " -n, --fib NUM 피보나치 n값 (기본: 45)"
echo " -u, --url URL 테스트 URL (기본: http://localhost/api/test/cpu/fibonacci)"
echo " -h, --help 도움말"
echo ""
echo "예시:"
echo " $0 -c 8 -n 40 # 동시 8개 요청, fib(40)"
echo " $0 --concurrent 12 # 동시 12개 요청"
exit 1
}
# 인자 파싱
while [[ $# -gt 0 ]]; do
case $1 in
-c|--concurrent)
CONCURRENT="$2"
shift 2
;;
-n|--fib)
N="$2"
shift 2
;;
-u|--url)
URL="$2"
shift 2
;;
-h|--help)
usage
;;
*)
echo "알 수 없는 옵션: $1"
usage
;;
esac
done
echo "============================================"
echo "CPU 부하 테스트 시작"
echo "============================================"
echo "URL: ${URL}?n=${N}"
echo "동시 요청 수: ${CONCURRENT}"
echo "============================================"
echo ""
# 밀리초 타임스탬프 함수 (macOS 호환)
get_ms() {
if [[ "$OSTYPE" == "darwin"* ]]; then
# macOS: perl 사용
perl -MTime::HiRes=time -e 'printf "%.0f\n", time * 1000'
else
# Linux: date 사용
date +%s%3N
fi
}
# 임시 파일 디렉토리
TEMP_DIR=$(mktemp -d)
trap "rm -rf $TEMP_DIR" EXIT
# 전체 시작 시간
TOTAL_START=$(get_ms)
# 동시 요청 실행
for i in $(seq 1 $CONCURRENT); do
(
START=$(get_ms)
RESPONSE=$(curl -s "${URL}?n=${N}")
END=$(get_ms)
WORKER=$(echo $RESPONSE | grep -o '"worker":[^,]*' | cut -d':' -f2 | tr -d '"')
PID=$(echo $RESPONSE | grep -o '"pid":[^,]*' | cut -d':' -f2)
DURATION=$(echo $RESPONSE | grep -o '"duration":"[^"]*"' | cut -d'"' -f4)
ACTUAL_DURATION=$((END - START))
echo "${i}|${WORKER}|${PID}|${DURATION}|${ACTUAL_DURATION}" > "${TEMP_DIR}/result_${i}.txt"
) &
done
# 모든 백그라운드 작업 대기
wait
# 전체 종료 시간
TOTAL_END=$(get_ms)
TOTAL_DURATION=$((TOTAL_END - TOTAL_START))
echo "결과:"
echo "--------------------------------------------"
printf "%-4s | %-8s | %-8s | %-12s | %-12s\n" "No" "Worker" "PID" "서버 응답" "실제 소요"
echo "--------------------------------------------"
# 결과 수집 및 출력
TOTAL_SERVER_TIME=0
TOTAL_ACTUAL_TIME=0
COUNT=0
for i in $(seq 1 $CONCURRENT); do
if [[ -f "${TEMP_DIR}/result_${i}.txt" ]]; then
IFS='|' read -r NO WORKER PID DURATION ACTUAL < "${TEMP_DIR}/result_${i}.txt"
printf "%-4s | %-8s | %-8s | %-12s | %-12s\n" "$NO" "$WORKER" "$PID" "$DURATION" "${ACTUAL}ms"
# 숫자만 추출해서 합산
SERVER_MS=$(echo $DURATION | tr -dc '0-9')
if [[ -n "$SERVER_MS" ]]; then
TOTAL_SERVER_TIME=$((TOTAL_SERVER_TIME + SERVER_MS))
fi
TOTAL_ACTUAL_TIME=$((TOTAL_ACTUAL_TIME + ACTUAL))
COUNT=$((COUNT + 1))
fi
done
echo "--------------------------------------------"
echo ""
echo "============================================"
echo "통계"
echo "============================================"
if [[ $COUNT -gt 0 ]]; then
AVG_SERVER=$((TOTAL_SERVER_TIME / COUNT))
AVG_ACTUAL=$((TOTAL_ACTUAL_TIME / COUNT))
echo "총 요청 수: ${COUNT}"
echo "전체 소요 시간: ${TOTAL_DURATION}ms"
echo ""
echo "서버 응답 시간 (평균): ${AVG_SERVER}ms"
echo "실제 소요 시간 (평균): ${AVG_ACTUAL}ms"
echo ""
echo "--------------------------------------------"
echo "분석:"
echo "--------------------------------------------"
# 싱글 vs 클러스터 판단
EXPECTED_SINGLE=$((AVG_SERVER * COUNT))
EFFICIENCY=$(echo "scale=2; $EXPECTED_SINGLE / $TOTAL_DURATION" | bc)
echo "싱글 모드 예상 시간: ${EXPECTED_SINGLE}ms (순차 처리)"
echo "실제 전체 시간: ${TOTAL_DURATION}ms"
echo "병렬 효율: ${EFFICIENCY}x"
echo ""
if (( $(echo "$EFFICIENCY > 1.5" | bc -l) )); then
echo "=> 클러스터 모드로 병렬 처리되고 있습니다!"
else
echo "=> 싱글 모드이거나 워커 수가 부족합니다."
fi
fi
echo "============================================"실제 테스트
싱글코어는 예상 시간: 69864ms (순차 처리) , 실제 전체 시간: 69977ms
yangsejin@yangsejin-ui-MacBookPro video-creater % sh scripts/load-test.sh --concurrent 12
============================================
CPU 부하 테스트 시작
============================================
URL: http://localhost:8080/api/test/cpu/fibonacci?n=45
동시 요청 수: 12
============================================
결과:
--------------------------------------------
No | Worker | PID | 서버 응답 | 실제 소요
--------------------------------------------
1 | single | 18 | 5779ms | 23216ms
2 | single | 18 | 5795ms | 11646ms
3 | single | 18 | 5785ms | 17436ms
4 | single | 18 | 5816ms | 5847ms
5 | single | 18 | 5786ms | 40620ms
6 | single | 18 | 5811ms | 29026ms
7 | single | 18 | 5803ms | 34834ms
8 | single | 18 | 5838ms | 58078ms
9 | single | 18 | 5883ms | 63972ms
10 | single | 18 | 5822ms | 46443ms
11 | single | 18 | 5791ms | 52240ms
12 | single | 18 | 5962ms | 69932ms
--------------------------------------------
============================================
통계
============================================
총 요청 수: 12
전체 소요 시간: 69977ms
서버 응답 시간 (평균): 5822ms
실제 소요 시간 (평균): 37774ms
--------------------------------------------
분석:
--------------------------------------------
싱글 모드 예상 시간: 69864ms (순차 처리)
실제 전체 시간: 69977ms
병렬 효율: .99x
=> 싱글 모드이거나 워커 수가 부족합니다.
============================================멀티코어 : 싱글 모드 예상 시간: 97884ms (순차 처리) 실제 전체 시간: 8421ms
============================================
yangsejin@yangsejin-ui-MacBookPro video-creater % sh scripts/load-test.sh --concurrent 12
============================================
CPU 부하 테스트 시작
============================================
URL: http://localhost:8080/api/test/cpu/fibonacci?n=45
동시 요청 수: 12
============================================
결과:
--------------------------------------------
No | Worker | PID | 서버 응답 | 실제 소요
--------------------------------------------
1 | 1 | 25 | 8311ms | 8345ms
2 | 6 | 31 | 8220ms | 8261ms
3 | 3 | 27 | 8147ms | 8187ms
4 | 12 | 78 | 7783ms | 7826ms
5 | 4 | 28 | 8169ms | 8202ms
6 | 7 | 36 | 8125ms | 8169ms
7 | 5 | 29 | 8356ms | 8395ms
8 | 9 | 50 | 8339ms | 8378ms
9 | 2 | 26 | 8099ms | 8151ms
10 | 11 | 69 | 8176ms | 8214ms
11 | 8 | 42 | 8100ms | 8141ms
12 | 10 | 64 | 8063ms | 8111ms
--------------------------------------------
============================================
통계
============================================
총 요청 수: 12
전체 소요 시간: 8421ms
서버 응답 시간 (평균): 8157ms
실제 소요 시간 (평균): 8198ms
--------------------------------------------
분석:
--------------------------------------------
싱글 모드 예상 시간: 97884ms (순차 처리)
실제 전체 시간: 8421ms
병렬 효율: 11.62x
=> 클러스터 모드로 병렬 처리되고 있습니다!
============================================계산하는 것을 봤을때 69977ms VS 8421ms 속도차이는 확실히 발생하는 것을 확인할 수 있었다.
다음 글에서는 실제 실무에서는 어떻게 수치들을 관리하고 모니터링하는지를 설명할건데
이렇게 일시적인 부분말고 실제 트래픽처럼 만들어서 오픈소스기반 모니터링툴을 이용해 보이겠다.
728x90
'JS' 카테고리의 다른 글
| Node.js 에서 멀티코어를 활용하자 - 4 - 실무처럼 테스트해보자 (0) | 2026.02.02 |
|---|---|
| Node.js 에서 멀티코어를 활용하자 - 실무에서 쓰자 개념 (0) | 2026.02.02 |
| Node.js 에서 멀티코어를 활용하자 - 개념 (0) | 2026.02.02 |
| TDZ (Temporal Dead Zone) (0) | 2026.01.19 |
| npm 호이스팅과 js 호이스팅 (1) | 2026.01.19 |