
이번 포스팅에서는 Spring Boot의 내장 서버로 활용할 수 있는 Tomcat
의 Thread Pool
에 관해 좀 자세히 알아보려고 한다.
Thread 개요
스레드, 스레드.. 컴공과 개발자를 꿈꾸는 사람이라면 수도 없이 들어봤을 그 이름이다.
Thread
는 Process
의 작업의 단위이다. 그리고 CPU Core가 Thread
단위로 작업을 처리하게 된다.
즉, 결론적으로 Thread
는 CPU Core의 실행 단위라고 할 수 있다.
Thread
를 사용함으로 하나의 Process
에서 두 가지 이상의 작업을 동시에 실행 가능하게 된다.
단순하게 Thread를 사용하게 된다면?
만약 새로운 요청이 들어올 때마다 새로운 Thread
가 생성된다고 가정해보자.
생성 비용이 큰 Thread
기본적으로, Thread는 생성 비용이 크다.
따라서, 새로운 요청이 들어올 때마다 새로운 Thread
를 생성하게 되면 응답시간이 늘어날 수 밖에 없다.

위의 사진을 참고로 Java를 예시로 들어보겠다.
- Java는 One-to-One Threading-Model로 Thread를 생성한다.
- User Thread(Process의 스레드) 생성 시 OS Thread(OS 레벨의 스레드)와 연결한다.
- 새로운 Thread를 생성할 때마다 OS Kernal의 작업이 필요하다.
- Thread는 생성 비용이 크다.
- 작업 요청이 들어올 때마다 Thread를 생성하게 되면 최종적인 요청 처리 시간이 증가한다.
좀 더 부연 설명을 하자면,
- 실제 사용자 애플리케이션이 새로운 스레드 생성을 요청한다.
- 운영 체제는 이 요청을 받아
TCB(Thread Control Block)
을 생성하고, CPU와 메모리 자원을 할당하며,
새로운 스레드에 대한 상태와 정보를 관리한다. - 스케줄링 큐에 새로운 스레드를 추가하고, 해당 스레드의 실행 준비가 되었다면 CPU에 할당한다.
위 작업이 새로운 요청이 들어올 때마다 수행된다면?
생성에도 시간이 오래걸리며, 많아진 Thread
때문에 Context Switching
에도 시간이 오래 걸릴 것이다.
Thread가 너무 많으면 여러 복합적인 문제가 발생
- 만약, Process의 처리 속도보다 빠르게 요청이 들어오면 새로운 Thread가 무한적으로 계속 생성
- Thread가 많아질수록 메모리를 많이 차지하며
Context-Switching
이 더 자주 발생 - 이러한 문제 때문에 메모리 문제가 발생하거나 CPU 오버헤드가 더 발생
- CPU 연산의 비중보다
Context-Switching
의 비중이 좀 더 많아짐
- CPU 연산의 비중보다
그래서, 위의 문제를 해결하는 것에 도움을 주는 것이 Thread Pool
이다.
Thread Pool로 문제를 해결
Thread Pool
이란 Thread
를 허용된 개수 안에서 사용하도록 제한하는 시스템이다.
또한, 한번 사용된 Thread
는 작업이 끝나고 삭제되는 것이 아니라 재사용할 수 있다는 특징이 있다.
Thread Pool
에는 크게 스레드와 작업 큐 두가지로 이루어져 있다.

Thread Pool
에 작업 처리를 요청- Task Queue(작업 큐)에 offer
- Task Queue에서 poll하여 각 스레드로 작업을 보내고, 스레드는 작업을 처리
- 결과 전달
즉, Thread Pool
을 사용하면 앞에서 봤던, 단순하게 Thread를 사용했을 때의 문제를 똑똑하게 해결할 수 있다.
- 새로운 요청마다 Thread를 생성함으로써 최종 요청 처리 시간이 증가
- 미리 만들어 둔 Thread를 재사용할 수 있기에 새로운 Thread 생성 비용이 절감
- 메모리 문제가 발생하거나 CPU 오버헤드가 발생하는 문제(너무 많은 스레드)
- 사용할 Thread의 개수를 제한하기 때문에 무제한적으로 스레드가 생성되지 않게한다.
요약하자면, 여러개의 작업을 동시에 처리하고 안정적으로 처리하고 싶다면 Thread Pool
이 아주 최적이라고 할 수 있다.
Java Thread Pool의 구성
Java는 ThreadPoolExecutor
를 클래스를 이용해 Thread Pool
을 구현하고 있다.
주요 특징
- maximumPoolSize
- keepAliveTime
- corePoolSize
Thread Pool
에서 항상 maximumPoolSize
값의 최대 개수로 Thread를 만들어두지 않는다.
왜냐? 요청이 적을 때 많은 양의 Thread를 들고 있는 것은 불필요하기 때문이다.
따라서, 관련된 최적화 기능들이 있다.
- 만약 작업이 없는데 스레드 개수가 최대로 있고
keepAliveTime
이후로도 계속 요청이 없다면, - 작업
Queue
에도 작업이 없는 것이므로, 스레드를corePoolSize
까지 없앤다.
그럼 이런 Thread Pool
이 어울릴 만한 곳은 어디일까?
바로 Web Server
이다.
새로운 요청이 계속 들어오며, 클라이언트는 자기 작업이 끝나면 언제 다시 접속할지 모르며, 많은 트래픽이 몰릴 수도 있다.
Thread Pool을 사용하는 Web Server
동시적인 요청을 동시적으로 처리하기 위해 많은 웹 서버들이 Thread Pool
을 사용한다.
Spring Boot
의 내장 Servlet Container
중 하나인 Tomcat
을 그 예시로 들 수 있다.
Tomcat
은 기본적으로 Java
기반의 WAS
이다.
따라서, Java
의 Thread Pool
과 매우 유사한 자체 Thread Pool 구현체
를 가지고 있다.
- 하지만,
Java
의ThreadPoolExecutor
의 경우queueSize
가 가득차면 이후 스레드가 증가한다. Tomcat
의 경우에는min-spare
보다 많은 요청이 들어오면maxConnections
과 요청을 처리할 스레드 수가 증가한다.

MaxConnection
MaxConnection
은 Tomcat이 최대로 동시에 처리할 수 있는 Connection 개수를 의미한다.
Web
요청이 들어오면 Tomcat의 Connector
가 Connection
을 생성하면서 클라이언트와 커넥션을 연결하게 되는데, 그때 요청된 작업을 Thread Pool
에 연결한다.
이때, 웹 서버가 동시에 커넥션을 연결해서 처리할 수 있는 최대 TCP/IP
개수가 MaxConnection
이다.
즉, 클라이언트가 서버에 연결할 수 있는 최대 연결 수(서버가 동시에 지원할 수 있는 클라이언트 연결의 수)
MaxThread
MaxThread
는 Tomcat이 최대로 동시에 처리할 수 있는 Thread 개수를 의미한다.
Web
요청이 들어와서 Thread Pool
에 작업처리를 요청하면, Task Queue
에 offer에 담겨 스레드가 작업 처리.
즉, 각 클라이언트의 요청을 처리하는 데 필요한 최대 Thread 수(실제 요청을 처리할 수 있는 능력)
AcceptCount
MaxConnection
이상의 요청이 들어왔을 때 사용하는 대기열 Queue의 사이즈를 의미한다.
MaxConnection
과 Accept-Count
요청이 들어왔을 때 추가적으로 들어오는 요청은 거절
Spring Boot 설정으로 Tomcat Thread Pool 설정
# application.yml (적어놓은 값은 default)
server:
tomcat:
threads:
max: 200 # 생성할 수 있는 thread의 총 개수
min-spare: 10 # 항상 활성화 되어있는(idle) thread의 개수
max-connections: 8192 # 수립가능한 connection의 총 개수
accept-count: 100 # 작업큐의 사이즈
connection-timeout: 20000 # timeout 판단 기준, 20초
port: 8080 # 서버를 띄울 포트번호
우리는 Spring Boot에서 내장 톰캣 Thread Pool
에 관한 설정을 할 수 있다.
server.tomcat.threads.max
Thread Pool
에서 사용할 최대 스레드 개수이고 기본값은 200이다.
서버 애플리케이션이 동시에 처리할 수 있는 요청 개수와 관련되어 있다.
- 요청 수에 비해 너무 높게 설정하면 -> 놀고 있는 스레드가 많아지므로 비효율이 발생
- 너무 적게 설정하면 -> 동시 처리 요청수가 줄어들어 대기가 길어져 평균 응답시간과 TPS가 감소한다.
기본적으로 Thread
가 너무 많아지면 CPU 오버헤드(Context Switching, 자원할당)과 메모리에서 문제가 생길 수 있다.
server.tomcat.max-connections
동시에 처리할 수 있는 최대 Connection의 개수, 기본값은 8192이다.
사실상 서버의 실질적인 동시 요청 처리 개수라고 생각할 수 있다.
(주의할 점은 이 수는 실제 연결된 Connection
수가 아닌 Socket File Descriptor
의 수이다.)
(살짝 헷갈리는 부분이긴 하다..)
어차피 처리할 수 있는 것은 스레드의 개수로 제한되어있는데 왜.. MaxConnections
는 엄청 많지..?
이유:
- Socket은 Connection이 끝난 후 바로 반환되는 것이 아니라 Connection 종료 후
FIN
신호,TIME_WAIT
시간을 거쳐 Socket의 Connection을 종료한다. - 또한,
HTTP
의KeepAlive
옵션을 사용하게 되면, 요청을 처리하지 않는Connection
수도 유지되기에,
요청 처리 수보다 실제 연결되어있는Connection
수가 높을 수 있다. - 위의 이유들로
maxConnections
는 넉넉하게 설정하는 것이 좋다고 한다.
이 부분에서 Tomcat
의 Connector
컴포넌트의 작업 방식에 따라 조금 달라진다고 한다.
Blocking I/O
- 우리에게 친숙한
Spring MVC
의 작업 방식 - 방식에서는 커넥션 하나가 하나의 Thread와 연결되어 작업한다.
- 따라서, 이 방식에서는
MaxThreadSize
와MaxConnection
개수가 항상 같아야 한다.- Tomcat 7 버전까지 적용된 방식
- 사실상
Blocking I/O
에서는 maxConnection도 200개라고 볼 수 있을 것 같다.
Non-Blocking I/O
Spring WebFlux
의 작업 방식- Thread 하나에 커넥션이 여러개 연결되어 동시적으로 작업을 처리
- 즉,
MaxThreadSize
보다MaxConnection
이 더 많아도 된다. - Tomcat 8 버전 이후부터 적용
- 즉,
- 하나의 Thread에 여러 커넥션을 연결하여 작업할 수 있으므로,
MaxConnectionSize <= MaxThreadSize
상황에 비효율
server.tomcat.accept-count
max-connections
이상의 요청이 들어왔을 때 사용하는 요청 대기열 Queue
의 사이즈, 기본값은 100이다.
(Blocking I/O 방식의 경우 maxThreadSize가 200개면 200개 이상부터 accept-count
에 쌓인다.
- 너무 높게 설정하면 -> 대기열이 커지면서 메모리 문제 유발
- 너무 작게 설정하면 -> 요청이 몰렸을 때 들어오는 요청들을 거절해버릴 수 있음
accept-count
설정을 하는 이유 중 하나는
부적절하거나 잘못된 요청이 한번에 너무 많이 들어왔을 경우 서버에 장애를 발생시키는 것을 방지하기 위함도 있다고 한다.
server.tomcat.threads.min-spare
Tomcat
이 유지하는 최소 스레드 수로, 예상되는 트래픽에 맞춰 이 값을 설정.
아까 살펴 본 CorePoolSize
처럼 대기 중인 요청이 없다면 이 min-spare
값만큼 대기 상태로 유지한다.
(나머지 스레드(maxThreads -minSpareThreads)는 Thread Pool
에 유휴 상태로 둔다.)
정리
Thread Pool
은 응답시간에 TPS에 영향을 주는 하나의 요소이다.
따라서, 잘 조정된 Thread Pool
은 시스템의 성능을 끌어내고 안정적인 애플리케이션 운용을 가능하게 한다.
보적절하게 설정된 Thread Pool
은 병목 현상, CPU 오버헤드, 메모리 문제를 유발한다.
'Spring > 유용한 정보' 카테고리의 다른 글
[성능 테스트] 성능 테스트 리팩토링 (0) | 2024.11.19 |
---|---|
[성능 테스트] Locust 활용 (0) | 2024.11.19 |
[성능 테스트] 성능 테스트 툴 (0) | 2024.11.18 |
[성능 테스트] 성능 테스트란 (0) | 2024.11.18 |
DB ERD 툴 (0) | 2024.11.12 |