[Tomcat] maxThreads, maxConnections, acceptCount로 Tomcat 튜닝하기

teo_99 2023. 9. 8. 23:42

우아한테크코스 레벨 4 미션을 진행하면서 Tomcat에 대한 학습을 진행하고 있습니다. 이 과정에서 accept count, max connection, max threads라는 키워드에 대해 알게 되었는데, 이번 아티클에서 개념과 사용법을 정리하고자 합니다.


 

들어가면서

max threads, accept count, max connection은 모두 HTTP 관련 Tomcat 속성입니다. 조금 더 디테일하게는 Tomcat의 여러 구성요소 중 HTTP Connector에 대한 속성입니다. 따라서 이런 속성 값들을 바꾸는 것은 HTTP Connector를 튜닝하는 것과 동일하다고 할 수 있겠습니다.

HTTP Connector는 특정 포트에서 HTTP 1.1 연결을 수신하는 역할을 합니다. 

 

maxThreads

Tomcat의 '최대' 스레드 개수를 의미합니다. 이는 동시에 HTTP Connector에서 처리할 수 있는 최대 요청 처리 수를 의미하기도 합니다. 즉, 동시에 얼마나 많은 요청을 처리할 수 있냐가 바로 이 maxThreads 프로퍼티에 의해 결정됩니다. 예를 들어 maxThreads 프로퍼티가 10으로 설정되어 있다면, 한번에 처리할 수 있는 최대 요청 개수는 최대 10개입니다. 
 

maxConnections

서버가 주어진 시간에 수락하고 처리할 수 있는 최대 커넥션 개수를 의미합니다. 기본값은 8192로 설정되어 있는데, 이는 8192개의 커넥션을 수락 및 처리할 수 있음을 의미합니다. 그리고 이렇게 수락 및 처리되는 커넥션들은 Tomcat Connector가 관리하는 큐에 저장됩니다.
 
만약 커넥션 요청이 너무 많이 들어와서, maxConnections에 설정된 값보다 더 많은 커넥션 요청이 들어오게 된다면 현재 수락 및 처리중인 커넥션 개수가 maxConections 값 이하로 떨어질 때까지 다른 요청들은 전부 운영체제가 관리하는 큐로 이동한 뒤 대기하게 됩니다.
 

acceptCount

maxConnections 값 이상의 커넥션 요청이 들어오게 되면 따로 운영체제가 관리하는 큐에서 대기하게 되는데, 이 큐의 사이즈가 acceptCount에 의해 결정됩니다. 이 큐에 들어온 커넥션 요청들은 '수락'만 된 상태이고 '처리'는 되지 않습니다. 기본 값은 100이므로 maxConnections 개수 이상의  요청이 들어오더라도 100개까지는 대기시킬 수 있다는 것을 의미합니다.
 

추가로, 어디까지나 운영체제가 관리하는 큐이기 때문에 acceptCount 설정을 무시하고 운영체제가 자체적으로 큐 사이즈를 결정하는 경우도 있다고 하니 유의하시면 좋을 것 같습니다.
 

그림으로 도식화한다면

그림으로 도식화하면 위와 같습니다. 스레드 풀에는 최대 maxThreads 값만큼의 스레드가 존재하고, Connector에 의해 관리되는 queue에 존재하는 커넥션 요청들을 처리하게 됩니다.
 
만약 Tomcat Connector 내부적으로 관리하는 큐의 크기(a.k.a maxConnections)를 넘어서서 더 많은 요청이 발생하는 경우, 이때부터는 운영체제가 관리하는 큐로 요청을 이동시키게 됩니다. 이 경우 커넥션 요청은 수락되지만 처리되지는 않습니다. 
 

실습

maxThreads, maxConnections, acceptCount의 값에 따라 어떻게 결과가 변하는지 여러 가지 상황을 통해 직접 확인해보겠습니다. 실습 환경은 다음과 같습니다.

  • Spring Boot를 사용하고 있음
  • 특정 엔드포인트에 HTTP 요청을 연속해서 10번 보내려고 함
  • 응답은 0.5초 뒤에 옴
  • 요청은 1초동안 처리되지 못하면 timeout 됨

maxConnection < maxThreads인 경우

maxThreads 값이 maxConnection보다 큰 경우를 살펴보겠습니다. 저희가 학습한 내용에 따르면 스레드 풀에 스레드 수가 아무리 많더라도 maxConnection의 값에 따라 제한이 될 수 있기 때문에 가용한 스레드 개수는 maxConnection 값을 따를 것입니다.

server:
  tomcat:
    threads:
      max: 10

    accept-count: 1
    max-connections: 1

실행 결과를 보시면 총 두 개의 요청만이 처리되었음을 확인할 수 있습니다. 첫번째 요청은 maxConnections 값이 1이므로 바로 처리되게 되고, 두번째 요청은 운영체제 큐에서 대기하다가 첫번째 요청이 끝난 이후에 처리되게 됩니다.
 
이 과정을 통해 maxThreads 값이 아무리 높아도 maxConnections 값에 따라 유휴(Idle) 스레드가 발생한다는 것을 확인할 수 있었습니다.
 

maxConnections > maxThreads인 경우

이 경우에는 유지할 수 있는 커넥션의 최대 개수가 스레드풀의 최대 스레드 개수보다 많으므로 가용한 모든 스레드가 사용될 것입니다. 

server:
  tomcat:
    threads:
      max: 9

    accept-count: 1
    max-connections: 10

스레드 번호를 보시면 1번부터 9번까지, 9개 모두 비슷한 시간 내에 사용되고 있습니다. 이를 통해 maxConnections 값이 maxThreads보다 컸을 때 비로소 유휴 스레드가 없이 모든 스레드가 사용된다는 것을 알 수 있었습니다.
 

총 요청 수가 maxConnections 값보다 클 때

이번에는 서버로 들어오는 요청 수가 maxConnections 값보다 클 때 어떻게 동작하는지 살펴보도록 하겠습니다. 학습한 내용에 따르면 당장 처리되지 못하는 요청들은 운영체제 큐로 가서 대기하게 되고, 해당 큐의 크기는 acceptCount 값을 따를 것입니다.

server:
  tomcat:
    threads:
      max: 1

    accept-count: 9
    max-connections: 1

실행 결과

스레드 개수는 최대 1개이고, 최대 커넥션의 개수도 1개이니 정해진 시간동안 서버는 단 하나의 요청만 처리하게 됩니다. 따라서 나머지 9개의 요청은 9의 크기를 가지는 운영체제 큐로 이동하게 되고 순차적으로 처리가 되게 됩니다. 0.5초 간격으로 처리되고 있는 모습을 확인할 수 있습니다.
 
이번에는 acceptCount의 값을 1로 설정해보겠습니다. 이 경우 큐에서 대기할 수 있는 요청이 최대 1개이므로 나머지 8개의 요청은 처리되지 못할 것입니다.

server:
  tomcat:
    threads:
      max: 1

    accept-count: 1
    max-connections: 1

실행 결과

운영체제 큐에서 대기하고 있던 요청을 포함해 총 두 개의 요청만 처리되는 것을 확인할 수 있습니다. 
 

참고 자료

https://tomcat.apache.org/tomcat-8.5-doc/config/http.html
https://velog.io/@sihyung92/how-does-springboot-handle-multiple-requests
https://bcho.tistory.com/788
https://velog.io/@byeongju/max-connections-accept-count-threads.max