이번 포스팅에서는 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`의 비중이 좀 더 많아짐
그래서, 위의 문제를 해결하는 것에 도움을 주는 것이 `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 |