지난번 작성했던 글에는 [ WAS 서버 직접 구현해보기(1) 동작 프로세스 이해 ]에 대해 다뤄봤습니다.
지난번의 내용을 요약한다면 이렇습니다.
backlog
란 스레드를 통해 작업이 이루어지기까지 클라이언트의 요청을 대기시키는 대기열입니다.TCP - 3way-handshake
과정에서 서버는 클라이언트 SYN 요청을 저장하는 SYN queue
와 소켓 accept를 waiting할 수 있는 accept queue
를 확인할 수 있었습니다.유휴 스레드(idle thread) queue
에서 대기중인 스레드가 작업 스레드(work thread) queue
로 이동되어 작업하게 됩니다.오늘은 WAS Server 성능 튜닝의 핵심인 스레드 풀(Thread Pool)에 대해서 알아보려합니다.
먼저 지난번 학습을 토대로 알게 된 내용과 접목하여 Thread
가 무엇인지 정의한다면 이렇게 말할 수 있을 것 같습니다.
스레드(Thread)란 클라이언트의 요청을 할당받은 자원으로
작업-처리하는 최소 단위
즉, 서블릿 요청을 처리하는 WAS 서버가 스레드와 긴밀한 관계를 맺고 있다는 것을 알 수 있습니다.
public class CustomWebApplicationServer {
public void start() throws IOException {
try (ServerSocket serverSocket = new ServerSocket(port)) {
Socket clientSocket;
while ((clientSocket = serverSocket.accept()) != null) {
new Thread(new ClientRequestHandler(clientSocket));
}
}
}
}
위의 코드는 사용자 요청이 accept 될 때 마다 새로운 쓰레드를 생성하여 request 를 핸들링하는 것을 확인할 수 있으며 다음과 같은 문제점들을 가지고 있습니다.
많은 수의 스레드를 필요로하는 비동기 프로그래밍에서 이러한 문제는
더욱 부각될 것입니다.
스레드 풀은 매 요청마다 새로운 스레드를 생성하는 것이 아닌
기존에 미리 설정한 스레드를 활용하는 것입니다.
즉, 스레드풀이란 스레드를 제한(Bounding)하고 관리(Managing)함으로서 안정적인 비동기 프로그램을 지원하는 것.
위의 1번 이미지에서 살펴볼 수 있듯 동작 플로우는 다음과 같습니다.
- accept 된 소캣 객체에 대한 task를 유휴 상태인 스레드가 받아 이를 처리한다
- 유휴 스레드가 없을 경우 작업 큐에 대기시킨다.
- 기존의 task 수행이 완료되면 작업큐에서 대기하고 있는 새로운 task의 요청을 스레드가 받는다.
Java 에서는 ExcutorService
를 활용하여 간단하게 스레드 풀을 구현할 수 있습니다.
ExcutorService
는 Runnable 인터페이스의 구현체에 스레드를 할당하여
작업을 실행하는 클래스입니다.
public class CustomWebApplicationServer {
private final int port;
private final ExecutorService executorService = Executors.newFixedThreadPool(10);
public CustomWebApplicationServer(int port) {
this.port = port;
}
public void start() throws IOException {
try (ServerSocket serverSocket = new ServerSocket(port)) {
Socket clientSocket;
while ((clientSocket = serverSocket.accept()) != null) {
executorService.execute(new ClientRequestHandler(clientSocket));
}
}
}
}
스레드 풀을 설정하는 부분을 더 살펴보겠습니다.
private final ExecutorService executorService = Executors.newFixedThreadPool(10);
public class Executors {
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public class ThreadPoolExecutor {
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
}
최종적으로 ThreadPoolExecutor
에 값을 설정하는것을 확인할 수 있는데
파라미터값의 정보는 다음과 같습니다.
corePoolSize: 스레드 풀의 핵심 스레드 개수를 지정합니다. 스레드 풀은 이 개수만큼의 스레드를 기본적으로 유지합니다.
maximumPoolSize: 스레드 풀에서 가질 수 있는 최대 스레드 개수를 지정합니다. corePoolSize보다 많은 작업이 들어올 때, 이 개수에 도달할 때까지 추가 스레드를 생성할 수 있습니다.
keepAliveTime: corePoolSize를 초과하는 추가 스레드가 작업을 마친 후 대기하는 시간을 지정합니다. 이 시간이 지나면 추가 스레드는 제거됩니다.
unit: keepAliveTime의 시간 단위를 지정합니다. 예를 들어, TimeUnit.SECONDS를 지정하면 keepAliveTime은 초 단위로 해석됩니다.
workQueue: 1번 이미지에서 살펴봤던 작업 큐입니다. 스레드 풀의 모든 스레드가 바쁠 때 추가로 들어오는 작업을 임시로 저장하는 공간입니다. 이 큐는 보통 BlockingQueue 인터페이스를 구현한 클래스를 사용하여 생성됩니다.
작업마다 직접 코드를 생성해서 스레드 풀 설정을 할 수 있지만
환경 구성으로도 스레드 풀을 컨트롤할 수 있습니다.
server:
tomcat:
accept-count: 5
max-connections: 150
threads:
max: 50
min-spare: 20
Q. 다음과 같은 환경 구성에서 만약 동시에 200개의 요청이 들어온다면?
디폴트 20개의 스레드는 추가적으로 30개의 스레드를 생성한다.(max 스레드 50 이므로)
50개의 요청을 처리하기 위해서는 tcp 연결이 유지되어야 하므로 max-connectios 이 커넥팅 할 수 있는 커넥션 수는 100이 된다.
나머지 150개의 요청에서 max-connections가 커넥팅할 수 있는 100이므로 나머지 50의 요청은 처리되지 않는다.
accept-count의 대기 수는 5이므로 나머지 50 요청중 45개의 요청은 거부된다.
스레드 풀을 튜닝하는데 있어서 중요한 몇가지 개념을 정리하였습니다.
작업을 완료한 스레드가 다음 작업을 빠르게 처리할 수 있도록 max-connections 크기를 늘려 미리 tcp 연결을 걸어둔다.
많은 시간이 소요되는 작업이거나 트래픽의 수가 그럼에도 감당할 수 없는 수준이라면 max-connections 대기중일 상태일때 time-out 일어날 수 있게되므로 주의해야 한다.
max-connections의 크기를 줄여 커넥션 대기중인 요청을 줄이고 accpt-count 를 늘려 요청을 백로그에 저장시켜 서버의 타임 아웃문제를 해결한다.
하지만 max-connections이 줄고 accept-count 의 수가 늘어난다면 그 만큼 tcp 요청을 해야하는 횟수가 많아진다.
tcp 요청은 꽤나 높은 비용을 유발하기에 성능이 느려질 수 있다.
그렇다면 accept-count의 수를 줄이고 max-thread를 늘리는 방식을 다시 고려해볼 수 있다. (무한 루프)
위의 시나리오를 정리해보면 결국 완벽한 해결책은 없고 상황에 맞게 값을 설정하여 적절한 밸런스를 유지해야하는 것을 알 수 있다.
_(물론 위 시나리오는 한 대의 was 서버이지만
실제적으로는 여러대의 서버가 있고 로드 밸런싱으로 헬스체크를하므로 이 부분과 세팅을 맞춰야 함.)
스레드 풀 이란?
스레드 풀 커스텀 설정은?
스레드 풀 성능 튜닝은?