Tomcat Connector와 JVM Thread

Tomcat HTTP Connector, thread pool, timeout, JVM 옵션을 Web-WAS 운영 관점에서 정리한다.


Tomcat 내부 요청 처리

Web Server / Reverse Proxy
  -> Tomcat Connector
  -> request processing thread
  -> Servlet / Application
  -> DB / external API

Tomcat에서 Connector는 요청을 받는 입구다. HTTP Connector는 보통 8080, AJP Connector는 보통 8009에서 요청을 받는다. 요청이 들어오면 Connector의 thread가 애플리케이션 코드를 실행한다.

왜 thread를 보는가

동기 요청 하나는 처리되는 동안 thread 하나를 잡고 있는 것으로 생각하면 이해가 쉽다. 애플리케이션이 DB 응답이나 외부 API를 오래 기다리면 thread도 같이 묶인다. 그래서 Tomcat thread 고갈은 앞단에서는 504, 사용자 입장에서는 느린 응답으로 보일 수 있다.

Connector 핵심 값

속성의미운영에서 보는 질문
maxThreads동시에 요청을 처리할 thread 상한요청 처리량과 backend 용량에 맞는가
minSpareThreads항상 준비해둘 thread 수급격한 요청 증가에 너무 늦게 반응하지 않는가
maxConnectionsConnector가 수용할 connection 상한연결은 많은데 thread가 부족한 상태인가
acceptCount처리 한계 이후 OS queue 길이queue가 차서 연결이 거부되거나 지연되는가
connectionTimeout연결 후 요청 라인을 기다리는 시간느린 client에 오래 묶이지 않는가
keepAliveTimeout다음 요청을 기다리는 시간keep-alive idle 연결이 너무 오래 남지 않는가
maxKeepAliveRequests연결 하나에서 처리할 요청 수너무 낮아 재연결이 많지 않은가

maxThreads를 키우면 동시에 실행되는 요청은 늘 수 있다. 하지만 DB connection pool이 작거나 외부 API가 느리면 thread만 많이 대기하게 된다. Tomcat, DB pool, upstream timeout을 같이 맞춰야 한다.

HTTP Connector 예시

<Connector port="8080"
           address="127.0.0.1"
           protocol="org.apache.coyote.http11.Http11NioProtocol"
           maxThreads="200"
           minSpareThreads="20"
           maxConnections="8192"
           acceptCount="100"
           connectionTimeout="20000"
           keepAliveTimeout="20000"
           maxKeepAliveRequests="100"
           redirectPort="8443" />

앞단 Nginx나 Apache HTTP Server가 같은 서버에 있다면 address="127.0.0.1"처럼 외부 노출을 줄이는 구성을 검토한다. 별도 서버에서 들어온다면 내부망 주소와 방화벽 정책을 같이 확인한다.

JVM 옵션 위치

Tomcat을 스크립트로 실행한다면 보통 $CATALINA_BASE/bin/setenv.sh에 JVM 옵션을 둔다. 서비스 매니저나 Windows Service로 실행하는 경우에는 systemd unit, 서비스 wrapper, 배포 스크립트가 옵션을 직접 줄 수 있으므로 실제 프로세스 인자를 확인한다.

ps -ef | grep tomcat
jcmd <PID> VM.flags
jcmd <PID> VM.command_line

운영에서 자주 확인하는 값:

항목예시
heap-Xms, -Xmx
OOM 근거-XX:+HeapDumpOnOutOfMemoryError, -XX:HeapDumpPath=...
GC logJava 버전에 맞는 GC log 옵션
timezone / encoding-Duser.timezone=Asia/Seoul, -Dfile.encoding=UTF-8
profile-Dspring.profiles.active=...

Tomcat 문서 기준으로 메모리 제한 같은 Tomcat 실행용 JVM 옵션은 CATALINA_OPTS 쪽에 두는 것이 낫다. JAVA_OPTS는 start뿐 아니라 stop 같은 다른 명령에도 적용될 수 있기 때문이다.

setenv.sh 예시

CATALINA_OPTS="$CATALINA_OPTS -Xms1g -Xmx1g"
CATALINA_OPTS="$CATALINA_OPTS -XX:+HeapDumpOnOutOfMemoryError"
CATALINA_OPTS="$CATALINA_OPTS -XX:HeapDumpPath=/var/log/tomcat"
CATALINA_OPTS="$CATALINA_OPTS -Duser.timezone=Asia/Seoul"

GC log 옵션은 Java 8, 11, 17에서 문법이 달라질 수 있다. 운영 서버의 Java 버전을 먼저 확인하고, 기존 옵션을 덮어쓰기보다 현재 기동 인자를 읽은 뒤 조정한다.

장애 때 보는 thread

jcmd <PID> Thread.print > /tmp/tomcat-thread.txt
jcmd <PID> GC.heap_info
ss -lntp | grep ':8080\|:8009'
curl -I http://127.0.0.1:8080/
tail -f $CATALINA_BASE/logs/catalina.out

thread dump는 한 번만 찍으면 순간 사진이다. 10초 간격으로 2~3번 찍어 같은 thread가 같은 DB 호출이나 외부 API 호출에 계속 머무는지 보는 편이 더 유용하다.

증상별 확인

증상확인 지점
504thread 고갈, DB slow query, 외부 API 지연, 앞단 timeout
503Tomcat down, Connector 한계, health check 실패
500application exception, DB connection 오류, 메모리 문제
간헐적 느림GC pause, 특정 API 지연, thread pool 대기
OOM 후 재기동heap dump, GC log, systemd restart, catalina log

관련 문서

관련 블로그 기록

공식 문서

닫기 전 질문

  • 앞단 Web Server가 바라보는 Tomcat 포트는 HTTP인가, AJP인가?
  • maxThreads, DB connection pool, 앞단 timeout은 서로 맞는가?
  • JVM 옵션은 setenv.sh, systemd, 서비스 wrapper 중 어디에서 주입되는가?
  • 장애가 났을 때 thread dump, GC log, heap dump가 남도록 준비되어 있는가?