연결 풀링과 Keep-Alive: 연결 비용 제거
HTTP 요청마다 TCP 연결을 새로 맺으면 3-way handshake + TLS handshake 비용이 발생합니다. Keep-Alive와 Connection Pool을 적절히 설정하면 이 비용을 대부분 제거할 수 있습니다.
연결 비용 이해
TCP 연결 없이 바로 요청 (Keep-Alive) → ~1ms
TCP 3-way Handshake → ~10ms (왕복 지연)
+ TLS 1.3 Handshake (1-RTT) → ~20ms 추가
+ TLS 1.2 Handshake (2-RTT) → ~40ms 추가
────────────────────────────────────
매 요청 신규 연결 총 비용 → ~50ms (TLS 1.2 기준)
초당 1,000 요청 서비스라면, Keep-Alive 없이 신규 연결만 맺으면 handshake에만 초당 50초 분량의 지연이 발생합니다.
Nginx upstream keepalive (Nginx ↔ 백엔드)
upstream backend {
server app1.internal:8080;
server app2.internal:8080;
server app3.internal:8080;
# 각 worker가 유지할 유휴 keepalive 연결 수
keepalive 64;
# keepalive 연결의 최대 요청 수 (이후 연결 재생성)
keepalive_requests 1000;
# 유휴 연결 유지 시간
keepalive_time 1h;
keepalive_timeout 60s;
}
server {
location / {
proxy_pass http://backend;
# Keep-Alive를 위해 반드시 HTTP/1.1 사용
proxy_http_version 1.1;
# Connection 헤더 제거 (HTTP/1.0 기본값 제거)
proxy_set_header Connection "";
}
}
클라이언트 ↔ Nginx Keep-Alive
http {
# 클라이언트 keepalive 유지 시간 (기본: 75s)
keepalive_timeout 65;
# keepalive 연결에서 처리할 최대 요청 수
keepalive_requests 1000;
# HTTP/1.0 클라이언트에도 keepalive 전송
keepalive_disable none;
}
Apache KeepAlive 설정
# /etc/apache2/apache2.conf 또는 sites-available/
# KeepAlive 활성화
KeepAlive On
# keepalive 연결 최대 요청 수 (0 = 무제한)
MaxKeepAliveRequests 1000
# 다음 요청 대기 시간 (초)
KeepAliveTimeout 5
# MPM Event 기준 연결 관리 (KeepAlive에 최적)
<IfModule mpm_event_module>
StartServers 2
MinSpareThreads 25
MaxSpareThreads 75
ThreadLimit 64
ThreadsPerChild 25
MaxRequestWorkers 150
MaxConnectionsPerChild 1000
</IfModule>
Tomcat 연결 풀 설정 (server.xml)
<!-- server.xml: Connector 설정 -->
<Connector port="8080"
protocol="HTTP/1.1"
<!-- 연결 큐 크기 (OS backlog) -->
acceptCount="100"
<!-- 최대 동시 연결 수 -->
maxConnections="10000"
<!-- 최대 처리 스레드 수 -->
maxThreads="200"
minSpareThreads="10"
<!-- Keep-Alive 타임아웃 (ms, -1 = 무제한) -->
keepAliveTimeout="20000"
<!-- keepalive 연결의 최대 요청 수 -->
maxKeepAliveRequests="100"
<!-- 연결 타임아웃 -->
connectionTimeout="20000"
<!-- 압축 설정 -->
compression="on"
compressionMinSize="2048"
compressableMimeType="text/html,text/xml,text/plain,text/css,
application/json,application/javascript"
/>
타임아웃 매트릭스
연결 체인의 각 구간에서 타임아웃을 일관성 있게 설정해야 502/504 오류를 방지할 수 있습니다.
클라이언트 ──[65s]──▶ Nginx ──[60s]──▶ Tomcat
server {
# 클라이언트와의 타임아웃
client_header_timeout 10s; # 요청 헤더 수신 타임아웃
client_body_timeout 30s; # 요청 바디 수신 타임아웃
send_timeout 30s; # 응답 전송 타임아웃
location / {
proxy_pass http://backend;
# 백엔드 연결 타임아웃
proxy_connect_timeout 5s; # 백엔드 TCP 연결 타임아웃
proxy_send_timeout 30s; # 백엔드로 요청 전송 타임아웃
proxy_read_timeout 60s; # 백엔드 응답 대기 타임아웃
# → Tomcat의 connectionTimeout(20000ms) < proxy_read_timeout(60s)
# Tomcat이 먼저 끊으면 Nginx는 502 반환
# Nginx가 먼저 끊으면 클라이언트는 504 반환
}
}
원칙: 백엔드 타임아웃 < Nginx 타임아웃 < 클라이언트 타임아웃
연결 풀 상태 모니터링
# Nginx에서 백엔드로의 연결 상태
# nginx_status 활성화 필요
curl http://localhost/nginx_status
# Active connections: 45
# server accepts handled requests
# 1234 1234 5678
# Reading: 0 Writing: 5 Waiting: 40 ← Waiting이 keepalive 유휴 연결
# Tomcat 스레드 상태 확인
curl http://localhost:8080/manager/status
# 또는 JMX로 확인
# ThreadPool: currentThreadCount, currentThreadsBusy
# OS 레벨 연결 상태
ss -s # 전체 소켓 요약
ss -tn | awk '{print $1}' | sort | uniq -c # 상태별 연결 수
# ESTABLISHED: 현재 활성 연결
# TIME_WAIT: 종료 대기 (너무 많으면 keepalive 미설정 의심)
# CLOSE_WAIT: 애플리케이션이 닫지 않은 연결 (누수 의심)
TIME_WAIT 과다 문제 해결
# TIME_WAIT 수 확인 (10,000 이상이면 문제)
ss -tn | grep TIME_WAIT | wc -l
# 해결 방법 1: keepalive 활성화 (근본 해결)
# 해결 방법 2: OS TCP 재사용 설정
# /etc/sysctl.conf
net.ipv4.tcp_tw_reuse = 1 # TIME_WAIT 소켓 재사용
net.ipv4.tcp_fin_timeout = 10 # FIN 대기 시간 단축 (기본 60s)
net.ipv4.ip_local_port_range = 1024 65535 # 사용 가능 포트 범위 확장
sudo sysctl -p # 즉시 적용