멀티스레딩 vs 스레드 풀: 실제 성능 테스트와 분석 진행
📌 서론
지금까지 열심히 java로 만든 http 요청 서버(멀티스레딩, 스레드 풀)를 JMeter를 사용하여 성능 비교를 해보려 한다. 이번 글을 통해 저부하부터 극한 부하까지 다양한 부하 상황에서 http 요청 서버(멀티 스레딩과 스레딩 풀)의 성능 측정 결과를 공유할 예정이다. 이 글을 읽은 후에는 tomcat이 왜 스레드 풀을 사용하는지 알 수 있게 될 것이다.
1. 성능 비교의 목적
목적
- 멀티스레딩 HTTP 서버와 스레드 풀 HTTP 서버의 성능을 비교하는 이번 실험의 주된 목적은 두 구현 방식의 효율성, 처리 능력 및 자원 사용의 효율성을 파악하는 것이다.
평가하고자 하는 내용
- 효율성
- 멀티스레딩 방식이 각 요청마다 스레드를 생성하고 소멸시키는 반면, 스레드 풀은 미리 생성된 스레드를 재사용한다. 이러한 차이가 실제 서비스 환경에서 어떤 영향을 미치는지 평가한다.
- 멀티스레딩 방식이 각 요청마다 스레드를 생성하고 소멸시키는 반면, 스레드 풀은 미리 생성된 스레드를 재사용한다. 이러한 차이가 실제 서비스 환경에서 어떤 영향을 미치는지 평가한다.
- 자원 관리
- 고성능 서버에서는 메모리 사용량과 CPU 사용률을 최적화하는 것이 중요하다. 스레드 풀이 이러한 자원을 보다 효과적으로 관리하는지 평가한다.
- 고성능 서버에서는 메모리 사용량과 CPU 사용률을 최적화하는 것이 중요하다. 스레드 풀이 이러한 자원을 보다 효과적으로 관리하는지 평가한다.
- 응답 시간 및 처리량
- 서버의 응답 시간과 처리량은 사용자 경험에 직접적인 영향을 미친다. 두 방식이 서버의 응답 시간과 처리량에 미치는 영향을 비교하여, 어느 방식이 더 나은 사용자 경험을 제공하는지 확인한다.
2. 테스트 환경 및 시나리오
테스트 환경
노트북 | M2 Max 64GB (로컬 환경) |
IDE | IntelliJ |
http 서버1 | java로 구현 (멀티스레딩) |
http 서버2 | java로 구현 (스레드 풀) |
사용 tool | JMeter (성능 측정) |
sublime | 각 테스트를 기록 |
테스트할 코드 정보
만약 코드가 궁금하신 분께서는 하단의 글에서 코드에 대한 설명을 보고 와주세요!
github에서 코드를 보셔도 됩니다.(http 패키지 내부에 multithread, threadpool 패키지 내부에 각각 코드가 존재합니다.)
JMeter를 사용하여 다음과 같이 4단계로 부하 테스트를 진행할 예정이다.
- 저부하 테스트 (500개 요청)
- 중간 부하 테스트 (2000 요청)
- 고부하 테스트 (5000개 요청)
- 극한 부하 테스트 (10000개 요청)
3. 저부하 테스트 진행 및 결과 분석
저부하 테스트 세팅 (스레드 500)
멀티스레드 테스트 결과
스레드풀 테스트 결과
테스트 결과
저부하 테스트 (500회) | 멀티스레딩 | 스레드 풀 |
총 요청 수 | 500회 | 500회 |
평균 응답 시간 | 1ms | 1ms |
중앙값 | 1ms | 1ms |
90%의 요청이 완료된 시간 | 2ms | 2ms |
95%의 요청이 완료된 시간 | 2ms | 3ms |
99%의 요청이 완료된 시간 | 24ms | 26ms |
최소 응답 시간 | 0ms | 0ms |
최대 응답 시간 | 35ms | 36ms |
오류 비율 | 0% | 0% |
처리량 | 초당 약 498.50개의 요청 | 초당 약 499.00개의 요청 |
수신 KB/sec | 59.39 KB/sec | 59.45 KB/sec |
송신 KB/sec | 77.40 KB/sec | 77.48 KB/sec |
성능 비교
- 응답 시간
- 멀티스레딩과 스레드 풀 방식 모두 유사한 평균 응답 시간과 중앙값을 보여준다. 이는 두 방식이 유사한 응답 시간을 가짐을 나타낸다.
- 멀티스레딩과 스레드 풀 방식 모두 유사한 평균 응답 시간과 중앙값을 보여준다. 이는 두 방식이 유사한 응답 시간을 가짐을 나타낸다.
- 99% Line (응답 시간)
- 99%의 요청이 완료되는 데 걸린 시간은 스레드 풀에서 약간 높다. 이는 스레드 풀 방식에서 극히 일부 요청이 멀티스레딩에 비해 약간 더 긴 지연됨을 보여준다.
- 99%의 요청이 완료되는 데 걸린 시간은 스레드 풀에서 약간 높다. 이는 스레드 풀 방식에서 극히 일부 요청이 멀티스레딩에 비해 약간 더 긴 지연됨을 보여준다.
- 처리량
- 스레드 풀 방식이 살짝 더 높은 처리량을 보였다. 하지만 이 차이는 매우 미미하며, 실제 운영 환경에서는 무시할 수 있을 정도다.
- 스레드 풀 방식이 살짝 더 높은 처리량을 보였다. 하지만 이 차이는 매우 미미하며, 실제 운영 환경에서는 무시할 수 있을 정도다.
- 최대 응답 시간
- 스레드 풀 방식의 최대 응답 시간이 멀티스레딩보다 약간 더 높다.
- 스레드 풀 방식의 최대 응답 시간이 멀티스레딩보다 약간 더 높다.
- 오류 비율
- 두 방식 모두 0%의 오류 비율을 보여준다. 이는 매우 안정적인 성능을 의미한다.
🫤 결론: 차이가 없네..?
저부하 테스트 결과 멀티스레딩과 스레드 풀 방식은 매우 유사한 성능을 보여주는 것을 알 수 있다. 두 방식 모두 안정적인 처리량과 낮은 응답 시간을 유지하고 있으며, 오류 없이 요청을 처리하는 능력이 뛰어나다.
따라서, 성능 측면에서 두 방식 간에 유의미한 차이는 나타나지 않는다. 이는 500회 정도의 저부하 수준에서는 스레드 관리 방식의 차이가 성능에 크게 영향을 미치지 않음을 보여준다.
4. 중간부하 테스트 진행 및 결과 분석
중간부하 테스트 세팅 (스레드 2000)
멀티스레드 결과
스레드풀 결과
테스트 결과
중간부하 테스트 (2000회) | 멀티스레딩 | 스레드 풀 |
총 요청 수 | 2000회 | 2000회 |
평균 응답 시간 | 0ms | 0ms |
중앙값 | 0ms | 0ms |
90%의 요청이 완료된 시간 | 1ms | 1ms |
95%의 요청이 완료된 시간 | 2ms | 1ms |
99%의 요청이 완료된 시간 | 8ms | 18ms |
최소 응답 시간 | 0ms | 0ms |
최대 응답 시간 | 33ms | 38ms |
오류 비율 | 0% | 0% |
처리량 | 초당 약 997.51개의 요청 | 초당 약 999.00개의 요청 |
수신 KB/sec | 118.84 KB/sec | 119.02 KB/sec |
송신 KB/sec | 154.89 KB/sec | 155.12 KB/sec |
성능 비교
- 응답 시간
- 두 방식 모두 매우 낮은 평균 및 중앙 응답 시간을 가지고 있다. 이는 두 서버 설정 모두 적은 지연으로 요청을 처리할 수 있음을 나타낸다.
- 두 방식 모두 매우 낮은 평균 및 중앙 응답 시간을 가지고 있다. 이는 두 서버 설정 모두 적은 지연으로 요청을 처리할 수 있음을 나타낸다.
- 처리량
- 스레드 풀 방식이 멀티스레딩보다 소폭 더 높은 처리량을 기록했다. 이 차이는 적지만 스레드 풀이 약간 더 효율적으로 요청을 처리할 수 있음을 알 수 있다.
- 스레드 풀 방식이 멀티스레딩보다 소폭 더 높은 처리량을 기록했다. 이 차이는 적지만 스레드 풀이 약간 더 효율적으로 요청을 처리할 수 있음을 알 수 있다.
- 최대 응답 시간
- 스레드 풀 방식의 최대 응답 시간이 멀티스레딩보다 약간 더 높다. 이는 스레드 풀에서 일부 요청이 더 오래 걸릴 수 있음을 의미한다.
- 스레드 풀 방식의 최대 응답 시간이 멀티스레딩보다 약간 더 높다. 이는 스레드 풀에서 일부 요청이 더 오래 걸릴 수 있음을 의미한다.
- 오류 비율
- 두 방식 모두 0%의 오류 비율을 보여주며, 이는 높은 안정성을 나타낸다.
🤨 결론: 잠깐! 아직도 별 차이가 없는데?
중간 부하 테스트 결과를 보면 아직도 멀티스레딩과 스레드 풀 방식은 매우 유사한 성능을 보여준다. 두 방식 모두 안정적인 처리량과 낮은 응답 시간을 유지하고 있으며, 오류 없이 요청을 처리하고 있다. 조금 더 부하를 줘서 테스트를 진행하자
5. 고부하 테스트 진행 및 결과 분석
고부하 테스트 세팅 (스레드 5000): 지금부터 이미지는 넣지 않고 결과만 설명한다.
고부하 테스트 (5000회) | 멀티스레딩 | 스레드 풀 |
총 요청 수 | 5000회 | 5000회 |
평균 응답 시간 | 3ms | 7ms |
중앙값 | 1ms | 0ms |
90%의 요청이 완료된 시간 | 5ms | 5ms |
95%의 요청이 완료된 시간 | 13ms | 101ms |
99%의 요청이 완료된 시간 | 21ms | 102ms |
최소 응답 시간 | 0ms | 0ms |
최대 응답 시간 | 351ms | 104ms |
오류 비율 | 8.22% | 3.76% |
처리량 | 초당 10683.76개의 요청 | 초당 10893.25개의 요청 |
수신 KB/sec | 3397.20 KB/sec | 2288.59 KB/sec |
송신 KB/sec | 1522.54 KB/sec | 1627.83 KB/sec |
성능 비교
- 응답 시간
- 멀티스레딩 방식의 평균 응답 시간이 스레드 풀 방식보다 더 낮다. 중앙값 또한 멀티스레딩이 스레드 풀보다 낮다. 하지만, 스레드 풀 방식의 95%와 99%의 응답 시간은 멀티스레딩보다 높은 것을 확인했다.
- 멀티스레딩 방식의 평균 응답 시간이 스레드 풀 방식보다 더 낮다. 중앙값 또한 멀티스레딩이 스레드 풀보다 낮다. 하지만, 스레드 풀 방식의 95%와 99%의 응답 시간은 멀티스레딩보다 높은 것을 확인했다.
- 처리량
- 스레드 풀 방식이 멀티스레딩보다 약간 더 높은 처리량을 기록했다.
- 스레드 풀 방식이 멀티스레딩보다 약간 더 높은 처리량을 기록했다.
- 오류 비율
- 멀티스레딩 방식에서 더 높은 오류 비율이 관찰되었다.
- 멀티스레딩 방식에서 더 높은 오류 비율이 관찰되었다.
- 최대 응답 시간
- 멀티스레딩 방식의 최대 응답 시간이 스레드 풀 방식보다 훨씬 더 높게 나타났다.
🤔 결론: 아까보단 조금 차이가 있는 것 같기도 하고..?
고부하 테스트 결과 멀티스레딩 방식은 평균적으로 더 낮은 응답 시간을 제공하지만 최대 응답 시간에서는 스레드 풀보다 더 긴 시간을 보인다. 이는 멀티스레딩 방식에서 일부 요청이 높은 지연을 경험할 수 있음을 나타낸다.
반면, 스레드 풀 방식은 전반적으로 일관된 응답 시간과 약간 더 높은 처리량을 제공하는 것을 알 수 있다. 두 방식 모두 낮은 오류 비율을 가지며, 전반적으로 높은 처리량을 유지한다. 하지만 멀티스레딩 방식은 최대 응답 시간에서 불규칙적인 결과를 보여줄 수 있으며, 스레드 풀 방식이 좀 더 일관된 성능을 제공하는 경향이 있다.
확실한 결과를 얻기 위해 극한 부하 테스트를 진행하고 결과를 분석해 봐야겠다.
6. 극한 부하 테스트 진행 및 결과 분석
극한 부하 테스트 세팅 (스레드 10000)
극한 부하 테스트 (10000회) | 멀티스레딩 | 스레드 풀 |
총 요청 수 | 10000회 | 10000회 |
평균 응답 시간 | 32ms | 4ms |
중앙값 | 1ms | 0ms |
90%의 요청이 완료된 시간 | 170ms | 1ms |
95%의 요청이 완료된 시간 | 205ms | 4ms |
99%의 요청이 완료된 시간 | 235ms | 151ms |
최소 응답 시간 | 0ms | 0ms |
최대 응답 시간 | 480ms | 154ms |
오류 비율 | 18.14% | 1.67% |
처리량 | 초당 약 8045.05개의 요청 | 초당 약 11481.06개의 요청 |
수신 KB/sec | 4488.39 KB/sec | 1831.31 KB/sec |
송신 KB/sec | 1022.58 KB/sec | 1752.93 KB/sec |
성능 비교
- 응답 시간
- 스레드 풀 방식이 멀티스레딩 방식보다 훨씬 낮은 평균 응답 시간을 보여준다. 중앙값과 90%, 95%, 99% 라인 또한 스레드 풀이 더 낮은 값을 가짐을 확인할 수 있다.
- 스레드 풀 방식이 멀티스레딩 방식보다 훨씬 낮은 평균 응답 시간을 보여준다. 중앙값과 90%, 95%, 99% 라인 또한 스레드 풀이 더 낮은 값을 가짐을 확인할 수 있다.
- 처리량
- 스레드 풀 방식이 멀티스레딩에 비해 더 높은 처리량을 기록했다.
- 스레드 풀 방식이 멀티스레딩에 비해 더 높은 처리량을 기록했다.
- 최대 응답 시간
- 멀티스레딩 방식의 최대 응답 시간이 스레드 풀 방식보다 훨씬 높다. 이는 멀티스레딩 방식에서 일부 요청이 더 긴 지연을 경험했다는 것을 의미한다.
- 멀티스레딩 방식의 최대 응답 시간이 스레드 풀 방식보다 훨씬 높다. 이는 멀티스레딩 방식에서 일부 요청이 더 긴 지연을 경험했다는 것을 의미한다.
- 오류 비율
- 스레드 풀 방식에서 오류 비율이 멀티스레딩 방식보다 훨씬 낮다.
🤩 드디어!! 확실한 차이가 나타났다.
극한 부하 테스트를 진행했더니 스레드 풀 방식이 이전과는 확연히 다른 차이점을 보여줬다. 스레드 풀 방식이 멀티스레딩 방식에 비해 전반적으로 더 우수한 성능을 보여준 것이다. 스레드 풀 방식은 더 낮은 응답 시간, 더 높은 처리량, 그리고 더 낮은 오류 비율을 가지며, 이는 스레드 풀이 스레드를 재사용함으로써 리소스 사용을 더 효율적으로 관리하고 있다는 것을 의미한다. 따라서, 높은 부하 상황에서 스레드 풀 방식이 더 안정적이고 효과적일 수 있다는 것을 알 수 있다.
7. 테스트 결과 정리
우리는 멀티스레딩과 스레드 풀을 사용한 java로 구성된 http 서버의 성능을 다양한 부하 상황에서 비교하였다. 4가지 부하 상황에 대한 테스트를 통해 우리가 얻은 핵심 발견 사항을 요약하면 다음과 같다.
- 응답 시간
- 스레드 풀 방식은 멀티스레딩에 비해 일관되게 더 낮은 평균 및 최대 응답 시간을 보여줬다. 이는 특히 높은 부하 상황에서 더욱 두드러졌다.
- 스레드 풀 방식은 멀티스레딩에 비해 일관되게 더 낮은 평균 및 최대 응답 시간을 보여줬다. 이는 특히 높은 부하 상황에서 더욱 두드러졌다.
- 처리량
- 스레드 풀을 사용했을 때, 서버의 처리량이 일관적으로 더 높았다. 이는 스레드 풀이 요청 처리를 위해 스레드를 효율적으로 재사용함으로써 리소스를 보다 효율적으로 활용할 수 있음을 의미한다.
- 스레드 풀을 사용했을 때, 서버의 처리량이 일관적으로 더 높았다. 이는 스레드 풀이 요청 처리를 위해 스레드를 효율적으로 재사용함으로써 리소스를 보다 효율적으로 활용할 수 있음을 의미한다.
- 오류 비율
- 높은 부하 상황에서 스레드 풀 사용이 오류 비율을 크게 줄이는 데 기여했다. 이는 스레드 풀이 시스템 자원을 보다 안정적으로 관리할 수 있음을 나타낸다.
🤩 정리
결과적으로, 스레드 풀은 고부하 상황에서 서버의 안정성과 성능을 개선하는 데 있어 필수적인 구성 요소임이 입증되었다.(다양한 조건에서의 테스트를 더 많이 진행해 봐야 확실해지겠지만 지금 결과를 봤을 때를 기준으로 말한다.) 따라서, 효율적인 서버 운영을 위해 스레드 풀의 사용을 적극적으로 고려해야 할 것이다.
사실 우리는 백엔드로 스프링과 톰켓을 사용하면서 이미 이것을 사용하고 있다. (톰캣)
8. 톰캣이 스레드 풀을 사용하는 이유
실제로 톰캣과 같은 웹 애플리케이션 서버가 스레드 풀을 사용하는 이유는 내가 수행한 테스트에서 관찰된 특성(고부하에 유리함)을 적용시키기 위해서다. 이러한 시스템(WAS)에서 스레드 풀을 사용하는 주된 이유는 다음과 같다.
- 성능 최적화
- 스레드 풀을 사용하면 스레드 생성 및 파괴에 대한 오버헤드가 감소한다. 미리 생성된 스레드를 재사용함으로써, 요청 처리 시간이 단축되고 시스템의 전반적인 성능이 향상된다.
- 스레드 풀을 사용하면 스레드 생성 및 파괴에 대한 오버헤드가 감소한다. 미리 생성된 스레드를 재사용함으로써, 요청 처리 시간이 단축되고 시스템의 전반적인 성능이 향상된다.
- 자원 관리
- 스레드 풀은 고정된 수의 스레드를 유지하므로, 자원 사용을 더욱 효율적으로 관리할 수 있다. 이는 메모리 사용량과 CPU 사용량을 안정화시키는 데 도움이 된다.
- 스레드 풀은 고정된 수의 스레드를 유지하므로, 자원 사용을 더욱 효율적으로 관리할 수 있다. 이는 메모리 사용량과 CPU 사용량을 안정화시키는 데 도움이 된다.
- 안정성
- 스레드 풀을 사용하면 고부하 상황에서도 서버의 안정성을 유지할 수 있다. 스레드 풀의 크기를 조정함으로써, 예상치 못한 트래픽 증가에도 빠르게 대응할 수 있다.
- 스레드 풀을 사용하면 고부하 상황에서도 서버의 안정성을 유지할 수 있다. 스레드 풀의 크기를 조정함으로써, 예상치 못한 트래픽 증가에도 빠르게 대응할 수 있다.
- 확장성
- 스레드 풀은 서버의 확장성을 개선한다. 새로운 요청이 들어올 때마다 새로운 스레드를 생성하는 대신, 미리 생성된 스레드를 효율적으로 재배치하여 처리할 수 있다.
- 스레드 풀은 서버의 확장성을 개선한다. 새로운 요청이 들어올 때마다 새로운 스레드를 생성하는 대신, 미리 생성된 스레드를 효율적으로 재배치하여 처리할 수 있다.
- 오버헤드 감소
- 스레드의 지속적인 생성과 파괴는 시스템에 부담을 줄 수 있다. 스레드 풀은 이러한 오버헤드를 줄여주고, 결국 더 많은 요청을 빠르고 효율적으로 처리할 수 있게 한다.
🤩 정리
내가 진행한 테스트는 이러한 장점들을 실제로 관찰하고 증명한 것이다. 그러나 테스트 환경, 테스트하는 애플리케이션의 종류, 트래픽 패턴 등에 따라 실제 운영 환경에서의 결과는 달라질 수 있다. 따라서, 실제 운영(프로덕션) 환경에서 스레드 풀의 효율성을 평가하기 위해서는 더 광범위하고 다양한 시나리오에서의 테스트가 필요하다.
9. 스레드 풀 사용으로 얻을 수 있는 효과
스레드 풀이 항상 멀티스레드 방식보다 성능 면에서 우월하다고 볼 수는 없다. 스레드 풀의 장점은 주로 시스템 자원의 효율적인 사용과 안정적인 성능을 유지하는 데 있다.
- 저부하 상황
- 스레드 풀이 멀티스레드 방식보다 성능상의 이점을 크게 느낄 수 없는 경우가 많다. 왜냐하면 저부하 상황에서는 스레드 생성 및 파괴에 따른 오버헤드가 전체 성능에 미치는 영향이 상대적으로 적기 때문이다.
- 실제로 저부하 상태에서는 새로운 스레드를 빠르게 생성하고 파괴하는 것이 큰 부담이 되지 않을 수 있다.
- 중간부하 상황
- 스레드 풀의 이점이 더 두드러지기 시작한다.
- 스레드 재사용으로 인한 오버헤드 감소가 성능 향상으로 이어질 수 있다.
- 고부하 상황
- 스레드 풀이 그 진가를 발휘한다. 많은 수의 동시 요청을 처리해야 하는 경우, 스레드 풀은 미리 생성된 스레드를 효율적으로 재사용하여 자원을 절약하고 성능을 안정화시킨다.
- 스레드 생성 및 파괴에 따른 오버헤드가 전체 성능에 큰 영향을 미치기 때문에, 스레드 풀이 성능을 크게 향상시킬 수 있다.
'JAVA' 카테고리의 다른 글
[OOP] 단일 책임 원칙, 응집도, 관심사 (12) | 2024.03.06 |
---|---|
[Java] HTTP 서버 만들기: 멀티스레딩과 네트워크 최적화하기 (39) | 2024.01.13 |
[Java] HTTP 서버 만들기: 스레드 풀(ThreadPool) 적용 (1) | 2024.01.02 |
[Java] HTTP 서버 만들기: 멀티스레딩 적용 (1) | 2024.01.02 |
[JMeter] MacOS(M1)에서 JMeter를 이용한 부하 테스트 (26) | 2024.01.01 |