[Performance Tuning] Nginx-Tomcat 최적화: 커넥션 풀(Connection Pool)의 임계값을 찾는 법
웹 서비스 성능의 병목은 대부분 '연결'에서 발생한다. 클라이언트의 요청을 받는 Nginx와 비즈니스 로직을 처리하는 Tomcat 사이의 통신 효율을 극대화하지 못하면, 서버 자원이 풍부해도 응답 속도는 처참해질 수 있다. 17년 차 아키텍트의 관점에서, 실전 환경의 부하를 견디는 Nginx-Tomcat 커넥션 풀 튜닝의 핵심 팩트를 정리한다.
1. Nginx: 업스트림 커넥션 유지 (Keepalive)
기본적으로 Nginx는 Tomcat(Upstream)으로 요청을 보낼 때마다 매번 새로운 커넥션을 맺고 끊는 'Short-lived connection' 방식을 사용한다. 이는 고부하 상황에서 TCP 3-Way Handshake 비용과 TIME_WAIT 소켓 고갈 문제를 야기한다.
Keepalive 설정: Nginx에서 Tomcat으로의 연결을 유지하여 소켓 재사용률을 높여야 한다.
Nginxupstream tomcat_cluster { server 127.0.0.1:8080; keepalive 32; # 연결을 유지할 최대 커넥션 수 }Proxy Protocol 준수:
proxy_http_version 1.1;과proxy_set_header Connection "";설정을 병행해야 HTTP/1.1의 Keepalive 특성을 온전히 활용할 수 있다.
2. Tomcat: Connector와 Thread Pool 최적화
Tomcat은 클라이언트(Nginx)의 요청을 받는 Acceptor와 실제 로직을 수행하는 Worker Thread로 구성된다.
maxThreads: 동시에 처리할 수 있는 최대 요청 수다. 무작정 높이면 Context Switching 비용으로 인해 CPU 부하만 가중된다. 보통 CPU 코어 수와 I/O 대기 시간을 고려해 결정한다.
acceptCount: 모든
maxThreads가 점유되었을 때 OS 레벨에서 대기시킬 요청 큐(Queue)의 길이다. 이 수치가 너무 크면 클라이언트는 타임아웃에 빠지고, 너무 작으면Connection Refused에러가 발생한다.maxConnections: 특정 시점에 서버가 유지할 수 있는 최대 연결 수다. NIO 방식을 사용할 경우
maxThreads보다 훨씬 크게 설정하여 비활성 커넥션을 효율적으로 관리할 수 있다.
3. HikariCP: DB 커넥션 풀의 'Golden Rule'
Java 진영의 표준인 HikariCP 설정은 전체 시스템 성능의 '라스트 마일'이다.
최적의 Pool Size 공식:
$$pool size = T_n \times (C_m - 1) + 1$$(여기서 $T_n$은 최대 스레드 수, $C_m$은 단일 쿼리 시 필요한 최대 커넥션 수다. 하지만 실무에서는 보통 (Core count * 2) + effective_spindle_count를 가이드라인으로 삼는다.)
minimumIdle vs maximumPoolSize: 성능을 위해서는 두 값을 동일하게 설정하여 커넥션 풀의 크기를 고정(Fixed)하는 것을 권장한다. 커넥션을 유동적으로 늘리고 줄이는 과정 자체가 런타임 오버헤드이기 때문이다.
4. 실전 튜닝 체크리스트 (Fact Check)
| 대상 | 주요 설정 항목 | 권장 전략 |
| Nginx | worker_connections | 프로세스당 최소 1024 이상, 시스템 ulimit 고려 |
| Nginx | keepalive_requests | 하나의 Keepalive 연결로 처리할 요청 수 (기본 100 -> 1000 이상 권장) |
| Tomcat | minSpareThreads | 상시 대기 스레드 수, 트래픽 급증 대비 최소 10~25 유지 |
| Tomcat | connectionTimeout | 20000(20초)보다 짧게 설정하여 좀비 커넥션 방지 |
| OS | net.ipv4.tcp_tw_reuse | 1로 설정하여 TIME_WAIT 소켓 재사용 허용 (리눅스 커널 튜닝) |
5. 아키텍트의 결론: "모니터링 없는 튜닝은 추측일 뿐이다"
설정값에 정답은 없다. **nari (Netstat)**나 VisualVM, 혹은 Prometheus/Grafana를 통해 현재 활성 스레드 수(Active Threads)와 커넥션 사용률을 모니터링하며 점진적으로 최적값을 찾아야 한다. 특히 앞서 언급한 **가상 스레드(Virtual Thread)**를 적용한다면, 기존의 maxThreads 공식은 완전히 재설계되어야 함을 명심하라.
댓글
댓글 쓰기