[Node.js]멀티 프로세스 vs 단일 프로세스 (pm2, autocannon)
(https://github.com/SHINDongHyeo/Node-JS-Performance-Test)[https://github.com/SHINDongHyeo/Node-JS-Performance-Test]
Node JS 부하테스트 툴 autocannon을 활용해 임의로 트래픽을 생성하고, 이를 해결하기 위해 pm2 모듈의 cluster를 활용해 멀티 프로세스 구현을 시도한다.
1. 테스트용 Nest JS api 만들기
node js의 웹 프레임워크 nest js 활용해 간단한 api를 만든다.
나는 GET /test 라는 경로로 cpu를 많이 사용하기 위해 복잡한 연산인 피보나치 수열 계산을 구현했다.
# app.service.ts 가운데
getTest(): number {
function fibonacci(num: number): number {
if (num <= 1) return num;
return fibonacci(num - 1) + fibonacci(num - 2);
}
const result = fibonacci(35); // 예: 35번째 피보나치 수 계산
return result;
}
2. pm2를 이용해 단일 프로세스 / 멀티 프로세스 구현하기
단일 프로세스
실행 명령어
pm2 start .\dist\main.js
멀티 프로세스
pm2 명령어를 통해 cluster 모드를 실행할 수도 있지만 ecosystem.config.js라는 파일을 이용해 실행하는 것이 더 편리하다고 하여 ecosystem.config.js 파일을 추가했다.
# ecosystem.config.js
module.exports = {
apps: [
{
name: 'performance-test',
script: 'dist/main.js', // 빌드된 JS 파일 경로
instances: 'max', // CPU 코어 수만큼 인스턴스 생성
exec_mode: 'cluster', // 클러스터 모드 사용
env: {
NODE_ENV: 'development',
},
env_production: {
NODE_ENV: 'production',
},
},
],
};
실행 명령어
pm2 start .\ecosystem.config.js
3. autocannon을 이용해 부하 생성하기
단일 프로세스
실행 명령어
autocannon -c 1000 -d 10 -p 10 http://localhost:3000/test
결과 화면
Running 10s test @ http://localhost:3000/test
1000 connections with 10 pipelining factor
┌─────────┬─────────┬─────────┬─────────┬─────────┬───────────┬───────────┬─────────┐
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
├─────────┼─────────┼─────────┼─────────┼─────────┼───────────┼───────────┼─────────┤
│ Latency │ 2901 ms │ 4104 ms │ 7970 ms │ 7970 ms │ 5432.9 ms │ 1499.2 ms │ 7970 ms │
└─────────┴─────────┴─────────┴─────────┴─────────┴───────────┴───────────┴─────────┘
┌───────────┬─────┬──────┬─────┬─────────┬───────┬───────┬───────┐
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
├───────────┼─────┼──────┼─────┼─────────┼───────┼───────┼───────┤
│ Req/Sec │ 0 │ 0 │ 0 │ 10 │ 2 │ 3.77 │ 1 │
├───────────┼─────┼──────┼─────┼─────────┼───────┼───────┼───────┤
│ Bytes/Sec │ 0 B │ 0 B │ 0 B │ 2.33 kB │ 466 B │ 878 B │ 233 B │
└───────────┴─────┴──────┴─────┴─────────┴───────┴───────┴───────┘
Req/Bytes counts sampled once per second.
# of samples: 10
21k requests in 12.8s, 4.66 kB read
10k errors (10k timeouts)
멀티 프로세스
실행 명령어
autocannon -c 1000 -d 10 -p 10 http://localhost:3000/test
결과 화면
Running 10s test @ http://localhost:3000/test
1000 connections with 10 pipelining factor
┌─────────┬─────────┬─────────┬──────────┬──────────┬────────────┬────────────┬──────────┐
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
├─────────┼─────────┼─────────┼──────────┼──────────┼────────────┼────────────┼──────────┤
│ Latency │ 2471 ms │ 9870 ms │ 10901 ms │ 10901 ms │ 9683.02 ms │ 1735.59 ms │ 10901 ms │
└─────────┴─────────┴─────────┴──────────┴──────────┴────────────┴────────────┴──────────┘
┌───────────┬─────┬──────┬─────┬─────────┬─────────┬─────────┬───────┐
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
├───────────┼─────┼──────┼─────┼─────────┼─────────┼─────────┼───────┤
│ Req/Sec │ 0 │ 0 │ 0 │ 30 │ 6.2 │ 11.69 │ 3 │
├───────────┼─────┼──────┼─────┼─────────┼─────────┼─────────┼───────┤
│ Bytes/Sec │ 0 B │ 0 B │ 0 B │ 6.99 kB │ 1.44 kB │ 2.72 kB │ 699 B │
└───────────┴─────┴──────┴─────┴─────────┴─────────┴─────────┴───────┘
Req/Bytes counts sampled once per second.
# of samples: 10
21k requests in 14.49s, 14.4 kB read
10k errors (10k timeouts)
테스트 결과
이상하게도 멀티 프로세스를 사용한 결과의 latency가 더 길었다. 프로세스를 병렬로 처리하고 있는데 왜 더 느려지는 걸까?
그 이유는 여러 가지로 추측된다.
- 시스템 자원 경쟁 병렬적으로 여러 프로세스가 동시에 실행되다보니 메모리 같은 컴퓨팅 리소스를 사용하기 위해 경쟁하다보니 오히려 성능 저하가 발생했을 확률
- 컨텍스트 스위칭 오버헤드 CPU가 여러 프로세스를 넘나들며 생기는 오버헤드가 오히려 성능에 문제를 줄 확률
- CPU 자원 부족 복잡한 연산같은 경우 CPU 자원을 많이 사용하는데 8개 프로세스가 병렬적으로 실행되다보니 각 프로세스가 사용할 수 있는 CPU 자원이 너무 적어 문제가 되었을 확률
이러한 추측을 기반으로 프로세스 숫자를 조금 줄여주면 어떨까라는 생각을 해봄
프로세스 개수 줄인 후 결과
기존 : max (CPU 코어 수 만큼 최대로 실행) 수정 후 : 2
결과 화면
Running 10s test @ http://localhost:3000/test
1000 connections with 10 pipelining factor
┌─────────┬─────────┬─────────┬─────────┬─────────┬───────────┬───────────┬─────────┐
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
├─────────┼─────────┼─────────┼─────────┼─────────┼───────────┼───────────┼─────────┤
│ Latency │ 3363 ms │ 3416 ms │ 4387 ms │ 4387 ms │ 3883.6 ms │ 494.32 ms │ 4387 ms │
└─────────┴─────────┴─────────┴─────────┴─────────┴───────────┴───────────┴─────────┘
┌───────────┬─────┬──────┬─────┬─────────┬───────┬─────────┬───────┐
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
├───────────┼─────┼──────┼─────┼─────────┼───────┼─────────┼───────┤
│ Req/Sec │ 0 │ 0 │ 0 │ 20 │ 4 │ 7.76 │ 1 │
├───────────┼─────┼──────┼─────┼─────────┼───────┼─────────┼───────┤
│ Bytes/Sec │ 0 B │ 0 B │ 0 B │ 4.66 kB │ 932 B │ 1.81 kB │ 233 B │
└───────────┴─────┴──────┴─────┴─────────┴───────┴─────────┴───────┘
Req/Bytes counts sampled once per second.
# of samples: 10
21k requests in 11.89s, 9.32 kB read
10k errors (10k timeouts)
latency가 감소한 결과를 확인할 수 있었다.
최종 결론
멀티 프로세스는 프로세스를 병렬적으로 동시에 실행하며 성능을 향상시킬 수 있는 방법 중 하나다. 하지만 무턱대고 멀티 프로세스 수만 늘린다고 성능이 향상될 수는 컴퓨팅 자원이나 여러 가지 상황에 맞춰서 적절한 숫자의 멀티 프로세스 수를 설정해주는 것이 중요하다.