[Spring] 서버 스펙에 따른 쓰레드 수 조정
최근 프로젝트를 하고 GCP(Google Cloud Platform)에 배포하여 운영 서버를 가동시켰다.
비용문제로 인해 낮은 스펙을 사용할 수 밖에 없었지만 아직 테스트 사용자만 있는 지금 상황에서는 충분한 스펙이라고 생각한다.
하지만 그럼에도 만족하지 못할 성능이 나오는 경우가 있다.
이번에는 이를 위해 서버 스펙에 맞춰 스레스 수를 조정한 과정을 공유하고자 한다.
그럼 스펙이 낮은 서버를 가동했을 때 Spring에서 고려해봐야 하는 것이 무엇일까?
톰캣 스레드
먼저 우리가 Spring 서버를 배포 할 때는 보통 Tomcat이라는 WAS를 통해 배포하게 된다.
Spring은 외부에 Tomcat을 가동한 상태에서 배포할 수도 있고, Spring Boot 내부에 내장되어 있는 Tomcat을 사용해서 배포할 수도 있다. 최근에는 내부에 내장되어있는 Tomcat을 통해 배포하는 방식이 주를 이루고 있다.
이렇게 가동한 Tomcat은 멀티 스레드 기반으로 요청을 처리한다.
멀티 스레드의 경우 요청을 병렬적으로 처리할 수 있기 때문에 대량의 다중 요청에 대해서 싱글 스레드보다 빠른 속도로 처리 할 수 있다는 장점이 있다.
하지만, 멀티 스레드 방식을 잘 활용하지 못한다면 싱글 스레드보다 못한 결과를 낳을 수 있다.
멀티 스레드가 병렬적으로 처리되려면
위에서 알 수 있듯, 멀티 스레드의 장점은 병렬성이다. 그런데, 정말 완벽히 병렬적으로 처리할 수 있을까?
컴퓨터가 연산을 처리하기 위해서는 CPU가 필요하다.
즉, 우리가 보낸 요청을 처리하기 위한 최종 목적지는 CPU다. 멀티 스레드의 요청을 동시에 처리하기 위해서는 CPU도 이 요청을 동시에 처리할 수 있어야 한다.
CPU에게 그럴 능력이 있을까?
CPU 코어

CPU 안에는 연산을 처리하기 위한 코어(Core)라는 것이 있는데, 이 코어의 개수에 따라 병렬적으로 처리하는 능력이 달라진다.
당연히 코어가 많을수록 처리 능력이 좋아진다.
위 사진을 보면 8코어 옆에 16스레드라고 적혀있다. 이 스레드는 논리적으로 나눈 코어라고 생각하면 쉽다.
컴퓨터의 운영체제는 코어가 16개인 것처럼 여기고 작업을 처리한다. 다시 말해, 코어 1개를 봤을 때 물리적으로 코어가 1개 있더라도 두 개의 스레드를 빠르게 번갈아가며 처리하여 마치 2개의 코어가 일을 처리하는 것처럼 나눈 것이다. (번갈아 실행하여 마치 동시에 실행하는 것처럼 처리하는 것을 동시성(Concurrency)라고 한다)
물론 번갈아 처리할 때 Context Switching이 일어나지만 1코어에 1개의 작업만 처리하도록 두는 것보다는 효율적일 것이다.
톰캣 스레드 조정
위 내용을 봤을 때 CPU의 병렬 처리 능력이 서버의 스레드 개수에 어떤 영향을 미칠지 알 것이다.
(Java의 thread는 kernel thread와 1:1로 mapping 되기 때문에 영향을 미친다)
현재 배포한 서버의 스펙을 한번 보자.

vCPU 2개, 메모리 2GB로 구성되어 있다.
vCPU는 가상 CPU인데 Hyper Threading 기술을 통해 CPU를 가상화하여 코어가 2개인 것처럼 작동하게 된다. 위와 다르게 병렬처리는 가능하겠지만, 실제 물리코어가 2개 있는 것보다는 성능이 약간 떨어질 것이다.
그 다음 Spring에서 설정된 기본 Tomcat 설정 값을 살펴보자.
server:
tomcat:
connection-timeout: 60s
max-connections: 8192
accept-count: 100
threads:
min-spare: 10
max: 200
- connection-timeout: 클라이언트가 새 연결을 열고, 요청을 보내기까지 기다리는 시간
- max-connections: 서버에서 accept & process 가능한 최대 connections 수
- accept-count: max-connections를 넘어서 연결 요청이 추가적으로 들어오는 경우 OS Level의 Queue에서 관리되는데, 이때 Queue의 최대 사이즈
- threads.min-spare: 항상 Running 상태를 유지하는 스레드의 최소 개수
- threads.max: 요청을 처리하기 위해서 Connector에서 생성할 수 있는 최대 스레드 개수
여기서 이번에 테스트 해 볼 것은 maxThreads 이다. 기본값은 200으로 설정되어 있는데, 현재 최대 가동 코어가 2개인 서버 스펙에서 최대 200개로 수행한다면 오히려 스레드 간에 경합이 많이 일어나 처리가 늦어질 가능성이 있다.
때문에 이 개수를 점점 낮춰가며 테스트를 해볼 것이다.
(물론 위에서 톰캣 설정 설명을 보고 알겠지만, max-connections의 개수가 maxThreads보다 수치가 낮다면 유의미한 결과를 얻지 못할 것이다)
다음은 ngrinder를 통해 테스트 한 결과이다. vUser 수를 바꿔가며 테스트하여 나온 결과의 평균치를 냈다.
| maxThreads | TPS | Latency | 응답 편차 |
| 200 | 51 | 1095ms | 불안정적 |
| 100 | 59 | 936ms | 불안정적 |
| 50 | 50 | 1081ms | 안정적 |
| 30 | 82 | 726ms | 안정적 |
| 20 | 70 | 795ms | 약간 불안정적 |
결과로 봤을 때, maxThreads가 30일 때 TPS가 가장 높게 나왔고 응답도 안정적이었다. 모든 api를 다 테스트 해본 것이 아니지만 평균적으로 비슷한 결과가 나와 30으로 설정하게 되었다.
'Backend > Spring' 카테고리의 다른 글
| [Spring] 좋아요 기능에 대한 동시성 문제 (4) | 2025.08.03 |
|---|---|
| [Spring] Spring REST Docs 도입기 (0) | 2023.05.10 |
댓글
이 글 공유하기
다른 글
-
[Spring] 좋아요 기능에 대한 동시성 문제
[Spring] 좋아요 기능에 대한 동시성 문제
2025.08.03 -
[Spring] Spring REST Docs 도입기
[Spring] Spring REST Docs 도입기
2023.05.10