1. 들어가며

Apache Tomcat은 Java 애플리케이션 서버(서블릿 컨테이너)로서, 웹 애플리케이션이나 RESTful API 서버를 구축할 때 매우 널리 사용되는 오픈소스 솔루션입니다. 개발 환경부터 운영 환경까지 폭넓은 분야에서 활용되는데, 톰캣의 기본 설정만으로도 간단한 서비스를 운영하는 데에는 큰 지장이 없지만, 트래픽이 많아지고 서비스의 규모가 커지면 성능 저하와 각종 장애가 발생하기 쉽습니다.

따라서 톰캣 서버에 대한 튜닝 옵션, 그리고 JVM 튜닝 전략을 잘 이해하고 적용해야 안정적이고 높은 성능의 웹·API 서비스를 제공할 수 있습니다. 이번 글에서는 최신 버전의 Apache Tomcat(2025년 1월 기준) 을 바탕으로, RESTful API 서버를 운영하는 시나리오에 초점을 맞춘 톰캣 튜닝 방법을 살펴보겠습니다.

2. 톰캣 환경 및 시나리오 개요

이번 시나리오는 HTTP/JSON 형식의 RESTful API 서버를 여러 대의 톰캣 인스턴스로 운영한다고 가정합니다. 앞단에는 L4 스위치를 배치하여 로드를 분산하고, 각 톰캣은 Stateless 형태의 서비스를 제공합니다. 애플리케이션 상태가 공유되지 않기 때문에, 스케일 아웃(scale-out)이 용이하며 유지보수도 수월하다는 특징이 있습니다.

2.1 톰캣 및 애플리케이션 특성

  • 서비스 형태: RESTful API (HTTP/JSON 통신)
  • Stateless: 세션(상태 정보) 없이 트랜잭션을 처리
  • 부하분산: L4 로드밸런서를 통해 여러 톰캣 인스턴스에 트래픽 분산
  • Tomcat 버전: 글 작성 시점을 기준으로, Tomcat 10.1.x 혹은 Tomcat 11.x(알파/베타 단계)도 참조 가능

2.2 튜닝 접근 방식

  1. 톰캣 자체의 커넥터 옵션 및 서버.xml 설정
  2. JVM 파라미터 및 GC 전략
  3. 운영 환경(운영체제 리소스, ulimit 등) 최적화
  4. 배포 구성(JAR, WAR, 톰캣 공용 라이브러리 관리 등)

이제 본격적으로 Tomcat 서버를 튜닝하는 주요 파라미터를 살펴보겠습니다.


3. server.xml 주요 튜닝 파라미터

톰캣의 대표적인 설정 파일인 server.xml${Tomcat_HOME}/conf/server.xml 경로에 있으며, 대부분의 중요한 설정들이 여기에 정의됩니다. 아래에서는 Listener와 Connector 설정을 중심으로 살펴보겠습니다.

3.1 Listener 설정

<Listener      className="org.apache.catalina.security.SecurityListener"      checkedOsUsers="root" />

`

  • SecurityListener: 톰캣 기동 시 특정 OS 사용자로는 기동이 불가능하도록 설정
  • checkedOsUsers=”root”: root 계정으로 기동을 막아주는 설정

운영 현장에서 종종 발생하는 실수 중 하나가, root 권한으로 애플리케이션 서버를 띄웠다가 재기동 시 permission 문제가 발생하는 경우입니다. root로 실행되면서 관련 파일들의 소유권이 root로 변경되었고, 일반 계정으로 재기동 시 파일에 접근할 수 없게 됩니다. 이 Listener를 통해 root로 실행되는 것을 원천 차단하면 해당 문제를 사전에 방지할 수 있습니다.

3.2 Connector 설정

<Connector      protocol="org.apache.coyote.http11.Http11NioProtocol"     port="8080"      ... />

톰캣은 BIO, NIO, APR 등 다양한 I/O 모델을 제공합니다.

  • BIO: 기본 Java I/O 기반
  • NIO: Java New I/O 라이브러리 사용
  • APR: Apache Portable Runtime(C 라이브러리)을 JNI로 연동 (가장 빠르지만, JNI 문제가 생기면 전체 프로세스가 중단될 수 있음)

Tomcat 10.x 이상에서는 기본 프로토콜이 NIO로 설정되어 있습니다. BIO는 예전 버전이나 호환성 목적이 아니라면 잘 사용하지 않는 추세이며, APR을 설치·사용하려면 추가 라이브러리 설정이 필요합니다. 성능·안정성을 모두 고려하면, NIO( Http11NioProtocol )을 권장합니다.

아래에서 Connector 내에 주로 튜닝해야 할 파라미터를 살펴봅시다.

3.2.1 acceptCount

acceptCount="10"
  • 의미: 서버가 처리할 수 있는 스레드가 모두 사용 중일 때, 요청을 대기시키는 큐의 길이
  • 설명: 큐에 쌓인다는 것은 이미 서버가 트래픽을 감당하기 어려운 상태임을 의미합니다. 큐 길이를 길게 두면 지연이 증가할 수 있으므로, REST API 특성상 요청-응답이 빠른 실패 처리가 유리한 경우, 보통 10 내외의 짧은 값을 권장합니다.

3.2.2 enableLookups

enableLookups="false"

  • 의미: 요청의 DNS 역조회 활성화 여부
  • 설명: true로 설정하면 IP를 DNS 이름으로 변환하기 위해 DNS 서버에 쿼리를 보내는데, 이는 불필요한 네트워크 지연을 초래할 수 있습니다. REST API 서버라면 대체로 IP 기반 처리가 충분하므로 false로 설정해 성능 저하를 막습니다.

3.2.3 compression

compression="off"

  • 의미: HTTP 응답을 gzip으로 압축하여 전송할지 여부
  • 설명: JSON 형태의 소규모 메세지라면, 압축을 적용해도 CPU 사용만 늘어날 뿐, 네트워크 절감 효과가 미미할 수 있습니다. 큰 이미지나 파일을 전송하는 시나리오가 아니라면 off로 두는 것이 일반적입니다.

3.2.4 maxConnections

maxConnections="8192"

  • 의미: 하나의 톰캣 인스턴스가 동시에 맺을 수 있는 최대 TCP 연결 수
  • 설명: TIME_WAIT 상태 등을 고려하면, 실제 동시 요청 개수보다 훨씬 많은 소켓 FD(File Descriptor)가 필요할 수 있습니다.
  • 주의점: OS 레벨에서 ulimit -n(파일 디스크립터 수)을 충분히 높게 설정해두지 않으면 maxConnections를 크게 잡아도 소용없습니다.

3.2.5 maxKeepAliveRequests

maxKeepAliveRequests="1"

  • 의미: HTTP Keep-Alive 연결에서 허용할 최대 요청 수
  • 설명: REST API 서버가 전형적인 Connectionless 환경이라면, KeepAlive 이득이 작을 수 있습니다. 응답 후 즉시 연결을 끊는 것이 대역폭 사용 측면에서 더 이점이 있을 때는 1로 설정해 KeepAlive를 사실상 비활성화합니다.

3.2.6 maxThreads

maxThreads="100"

  • 의미: 처리 가능한 최대 쓰레드 수(= 동시 요청 처리 개수)
  • 설명: 톰캣 내부에서 요청을 처리하는 실제 워커 쓰레드의 수입니다. 일반적으로 100 내외부터 트래픽 양, DB 부하 등을 고려하여 50~500 사이로 조정합니다. 너무 크게 설정하면 문맥 전환(Context Switch)이 많아져 성능이 저하될 수 있으므로, 성능 테스트를 통해 최적값을 찾습니다.

3.2.7 tcpNoDelay

tcpNoDelay="true"

  • 의미: TCP 패킷을 더 빠르게 전송할지 여부(Nagle 알고리즘 비활성화)
  • 설명: 작은 패킷도 즉시 전송하여 지연을 줄이는 반면, 트래픽은 증가합니다. 요즘은 네트워크 속도가 좋아서 대부분 true로 설정하여 즉시 응답을 유도하는 편입니다.

4. Tomcat 라이브러리 배포 전략

4.1 공통 라이브러리 공유

  • WAR 내부의 JAR: 일반적으로 WEB-INF/lib 아래에 각종 JAR 파일을 넣어 배포합니다.
  • Tomcat 공용 lib: 여러 WAR에서 공통으로 쓰는 라이브러리는 ${TOMCAT_HOME}/lib에 배치하면 메모리 사용 효율이 좋아집니다.
  • 장단점:
    • 장점: 공통 라이브러리는 한 번만 로딩되어 여러 WAR가 공유 → 메모리 절약
    • 단점: lib 디렉토리의 JAR가 수정될 경우 톰캣 재기동 필요 → 무중단 배포 어려움

운영 환경에서 여러 개의 WAR를 하나의 톰캣에 올릴 때, 같은 라이브러리를 중복 배포하지 않도록 관리가 필요합니다. 일반적으로 중요하거나 공통 사용도가 높은 라이브러리는 톰캣의 lib 디렉토리로 옮기는 방식이 쓰입니다.


5. 최신 버전 Tomcat에서 유의할 점

Tomcat 10.1.x 이상(또는 11.x)부터는 Servlet 6.0 규격 등을 지원하며, 일부 패키지 네이밍이 JakartaEE 쪽으로 변경되었습니다. 기존 javax 패키지가 jakarta로 바뀐 점이 대표적인 예입니다. 기존의 서블릿/JSP 기반 애플리케이션을 마이그레이션할 때 패키지 호환성 문제가 발생할 수 있으므로 유의해야 합니다.

5.1 HTTP/2 지원

Tomcat 10.0 이상에서는 HTTP/2 프로토콜을 지원합니다. 이를 활용하면 특정 상황에서 성능 향상을 기대할 수 있으나, 아래 조건이 필요합니다.

  • TLS(SSL) 설정 필수(HTTP/2는 HTTPS 기반으로 동작해야 호환성이 좋음)
  • ALPN(Application-Layer Protocol Negotiation) 라이브러리 설치 필요
  • 최신 브라우저/클라이언트에서 HTTP/2 요청이 가능해야 함

REST API 서버에서 HTTP/2를 쓰면, 요청-응답 효율성이 높아질 수 있지만 설정 복잡도가 높습니다. 대규모 트래픽 환경에서 테스트를 거쳐 적용하길 권장합니다.

5.2 AJP Connector

과거에는 Apache httpd와 연동하기 위해 AJP 프로토콜을 많이 썼지만, 현대 환경에서는 HTTP/HTTPS 프록시나 로드밸런서를 사용하는 것이 일반적입니다. Tomcat 10.x 이상에서도 AJP 커넥터는 제공되지만 보안 문제를 이유로 기본 설정이 많이 제한되었습니다. 특별한 이유가 없다면 HTTP/HTTPS로 통신하는 것이 간편하며, 역방향 프록시(Reverse Proxy) 설정이 더 일반화되어 있습니다.


6. JVM 튜닝

Java 애플리케이션에서 JVM 튜닝은 매우 중요합니다. 아래에서 대표적인 JVM 옵션과, 그 배경이 되는 기본 원리를 정리해보겠습니다.

6.1 서버 모드

-server

  • 의미: JVM이 서버 애플리케이션에 맞게 동작하도록 설정
  • 설명: 서버 모드로 동작하면 JIT(Just-In-Time) 컴파일러가 서버 용도에 최적화된 방식으로 코드를 최적화합니다. 클라이언트 모드와 달리, 장기간 구동, 빈번한 트랜잭션 처리에 최적화된 GC 및 메모리 관리를 수행합니다.

6.2 메모리 할당 옵션

-Xmx1024m -Xms1024m -XX:MaxNewSize=384m -XX:MaxPermSize=128m

  • -Xms / -Xmx: 최소/최대 힙(Heap) 크기. 서버 환경에서는 보통 두 값을 동일하게 설정하여, 힙 크기를 동적으로 늘렸다 줄이는 과정을 없앱니다.
  • New / Old 영역 비율: 서버 애플리케이션은 ephemeral(덧없는) 객체가 많으므로, New 영역을 충분히 배정하는 것이 유리합니다. 예: New : Old = 1 : 2 정도가 일반적입니다.
  • PermSize (또는 MetaspaceSize): 클래스 메타데이터가 올라가는 영역. 톰캣이 로딩해야 할 클래스 수에 비례하여 설정하면 되는데, 대규모 애플리케이션이 아니라면 128~256MB 수준으로 충분합니다. (Java 8 이상에서는 PermGen이 Metaspace로 변경되었으나, 유사한 개념이므로 혼동하지 않도록 주의합니다.)

6.2.1 32bit vs 64bit JVM

  • 32bit JVM: 최대 4GB(실질적으로 2GB 사용 가능) 메모리 제한.
  • 64bit JVM: 메모리 제한이 훨씬 크지만, GC 시간 증가 문제 등이 있어 너무 큰 힙 설정은 권장하지 않습니다. 2~3GB 정도까지가 무난하며, 그 이상 메모리가 필요하다면 톰캣 인스턴스를 여러 개 두고 클러스터링 혹은 로드밸런싱을 하여 운영하는 편이 안정적입니다.

6.3 OutOfMemoryError 대응

-XX:-HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./java_pid<pid>.hprof

  • 의미: OutOfMemoryError 발생 시 해당 시점의 Heap Dump를 자동으로 남김
  • 설명: OOM 발생 시점에 메모리 상태를 파악하는 것이 원인 파악에 매우 유리합니다. 이를 통해 누수(Leak)를 일으키는 객체가 무엇인지, 클래스 로더 문제가 무엇인지 확인할 수 있습니다.

6.4 GC(Garbage Collection) 옵션

  • Parallel GC + Concurrent GC: 현대 자바 서버에서는 거의 표준으로 자리 잡았습니다. GC를 병렬(Parallel)로 처리하면서, 애플리케이션 스레드와 동시(Concurrent)로 마킹을 수행하는 CMS(또는 G1 GC)를 조합해서 사용하면, Stop-The-World 시간을 크게 줄일 수 있습니다.

아래는 일반적인 CMS 혹은 G1 GC 사용 예시입니다. -XX:+UseConcMarkSweepGC -XX:ParallelGCThreads=2

혹은 Java 11 이상에서 G1 GC를 사용하려면:

-XX:+UseG1GC -XX:MaxGCPauseMillis=200

  • ParallelGCThreads: GC를 수행할 때 사용할 스레드 수. CPU 코어 개수의 절반 내지는 적절한 값을 부여합니다.
  • G1GC: 대규모 메모리 환경에서 우수한 성능을 보이며, Java 9 이상에서 기본 GC로 채택된 이력도 있습니다.

6.5 GC 로그 옵션

-XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+TraceClassUnloading -XX:+TraceClassLoading

  • 의미: GC 발생 시점을 기록하고, 메모리 사용량 및 GC 상세 정보를 로깅
  • 설명: 운영 환경에서 GC 로깅은 필수입니다. GC 로그를 통해 서브시스템의 메모리 상태와 GC 동작을 정확히 파악할 수 있습니다.
  • TraceClassLoading / Unloading: OutOfMemoryError가 클래스 로딩 문제로 발생할 때 분석에 유용합니다.

7. 운영 환경(운영체제)에서의 추가 고려 사항

7.1 파일 디스크립터 수 (ulimit)

  • ulimit -n: 프로세스가 열 수 있는 최대 파일(소켓) 디스크립터 수
  • 설명: Linux 환경에서 default로 1024~4096 사이로 제한되는 경우가 있습니다. 대규모 트래픽에서 maxConnections를 8,192로 잡았다면, OS 설정도 이에 맞춰 높여줘야 합니다.
  • 설정 방법:
    1. /etc/security/limits.confnofile(열 수 있는 파일 수) 항목을 늘린다.
    2. 셸에서 ulimit -n 100000 등으로 세션별 설정이 가능하다.

7.2 TCP 포트 범위 및 TIME_WAIT

  • TIME_WAIT: TCP 연결 종료 후 일정 시간 동안 소켓을 유지하는 상태. 동시에 많은 요청이 발생하면 TIME_WAIT 소켓이 매우 많이 쌓일 수 있습니다.
  • 해결책:
    • net.ipv4.tcp_tw_reuse, net.ipv4.tcp_tw_recycle 등의 커널 파라미터 조정(단, 최신 리눅스에서는 tcp_tw_recycle가 deprecated).
    • 포트 범위(net.ipv4.ip_local_port_range) 확대.

7.3 CPU/코어 할당

톰캣 인스턴스 여러 개를 띄우거나 JVM GC가 병렬로 동작할 때, CPU 코어 수가 충분해야 합니다. 코어 수가 부족하면 GC 스레드가 과도하게 CPU를 점유하여 메인 워커 스레드가 응답 지연을 일으킬 수 있으니, 인스턴스 수와 코어 수, GC 스레드 수 사이의 밸런스를 잡는 것이 중요합니다.


8. 실제 운영 시나리오 예시

8.1 예시 설정값

가정: 하루 50만 건의 API 요청, 초당 최대 30~40 TPS(Traffic Per Second), 4코어 8GB 메모리의 VM 서버에서 톰캣을 2대 띄운다고 합시다.

  1. Connector
    <Connector protocol="org.apache.coyote.http11.Http11NioProtocol" port="8080" acceptCount="10" maxThreads="200" enableLookups="false" compression="off" maxConnections="4096" tcpNoDelay="true" maxKeepAliveRequests="1" />

    • TPS가 40 이하이므로 maxThreads는 200 정도로 충분.
    • acceptCount=10으로 대기 큐를 짧게 유지, 초과 트래픽은 빠른 실패 처리.
    • keep-alive는 비활성화(또는 최소화).
  2. JVM (setenv.sh 혹은 시작 스크립트에 추가)

    CATALINA_OPTS="$CATALINA_OPTS -server" CATALINA_OPTS="$CATALINA_OPTS -Xms2048m -Xmx2048m" CATALINA_OPTS="$CATALINA_OPTS -XX:NewSize=512m -XX:MaxNewSize=512m" CATALINA_OPTS="$CATALINA_OPTS -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=128m" CATALINA_OPTS="$CATALINA_OPTS -XX:+UseG1GC" CATALINA_OPTS="$CATALINA_OPTS -XX:ParallelGCThreads=2" CATALINA_OPTS="$CATALINA_OPTS -XX:+PrintGCDetails -XX:+PrintGCDateStamps" CATALINA_OPTS="$CATALINA_OPTS -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tomcat"

    • 메모리를 2GB로 할당, G1GC 사용.
    • GC 로그 및 HeapDump 설정을 통해 장애 시 분석 가능.
  3. 운영체제

    • ulimit -n 65535 이상으로 설정
    • /proc/sys/net/ipv4/ip_local_port_range 수정 (예: 1024 65535)
    • TIME_WAIT 문제 최소화를 위해 커널 파라미터 일부 조정

8.2 모니터링 및 로깅

  • GC 로그: Heap 사용량 추이, GC 빈도, Pause Time 확인
  • Access 로그: 요청당 처리 시간(TTFB), 응답 상태 코드, 트래픽 추이
  • 애플리케이션 로그: 예외 발생 건수, OutOfMemoryError 여부
  • 시스템 지표: CPU 사용률, 메모리 사용률, 네트워크 I/O, Disk I/O

8.3 장애 시 대처

  • OOM 에러 발생HeapDumpOnOutOfMemoryError로 남겨진 Heap Dump 분석 → 누수 객체(Static, ThreadLocal 등) 확인
  • CPU 100% 문제 → GC가 자주 발생하는지, 혹은 애플리케이션 비즈니스 로직에서 무한 루프 등이 있는지 모니터링
  • Socket/FD 부족 → ulimit 조정, maxConnections 점검, Keep-Alive 설정 재검토

9. 결론 및 마무리

지금까지 Apache Tomcat 서버.xml 튜닝과, JVM 메모리·GC 튜닝에 대해 살펴보았습니다. 특히 RESTful API 서버 시나리오에서는 높은 TPS를 안정적으로 처리하기 위한 스레드, 큐, 커넥션 수 설정이 매우 중요합니다. 또한, JVM은 톰캣의 근간이 되는 실행 환경이므로, 적절한 힙 크기와 GC 방식을 선택해야 합니다.

핵심 정리

  1. Connector 튜닝: maxThreads, acceptCount, enableLookups, compression, maxConnections, tcpNoDelay
  2. JVM 메모리: -Xms, -Xmx, NewSize, PermSize(Metaspace), GC 방식(CMS, G1GC 등)
  3. 운영체제 설정: ulimit -n, TCP TIME_WAIT 최소화, CPU 코어 당 인스턴스 수
  4. 배포 구조: 여러 WAR vs 단일 톰캣 vs 여러 톰캣, lib 공유 여부
  5. 모니터링 및 로그 분석: GC 로그, Access 로그, Heap Dump 등을 통해 성능 병목과 장애 원인 파악

실무에서 톰캣 튜닝은 퍼포먼스 테스트부하 테스트를 통해 검증되는 것이 가장 정확합니다. 벤더나 오픈소스 커뮤니티의 가이드라인도 참고하여, 서비스 특성에 맞는 최적의 설정값을 찾는 과정이 중요합니다. Red Hat이나 다른 서드파티 업체를 통해 상용 서포트를 받는 방법도 있으며, 최신 Tomcat 릴리스 노트나 공식 문서를 꼼꼼히 확인해 두는 것이 좋습니다.

: Tomcat 10.x~11.x의 공식 문서
Apache Tomcat 10.1 문서
Apache Tomcat 11 (Preview)

이 글에서는 톰캣 서버 튜닝의 핵심만 압축해 설명하였지만, 실제 환경은 훨씬 더 복잡할 수 있습니다. 네트워크, 운영체제, DB, 애플리케이션 로직 등 다양한 요소가 복합적으로 얽히므로, 전체적인 시스템 아키텍처를 염두에 두고 각 요소를 모니터링하며 튜닝해야 가장 최적화된 성능을 낼 수 있습니다.

앞으로도 새로운 톰캣 버전이 릴리스되면, IO 모듈이나 GC 방식, HTTP/2 및 TLS 설정 등에서 변화가 있을 수 있으니, 버전업 추적 및 테스트 환경에서의 검증을 게을리하지 않는 것이 중요합니다.


참고 자료

  1. Apache Tomcat 공식 문서
  2. Red Hat JBoss Web Server 튜닝 가이드 (Red Hat 지원 시)
  3. Oracle Java Performance Tuning Guide

태그:

카테고리:

업데이트:

댓글남기기