트래픽이 폭주할 때 CPU와 메모리 자원은 충분한데 시스템이 응답하지 않는 상황을 겪어본 적이 있는가. Nginx 에러 로그에 socket() failed (24: Too many open files) 또는 Cannot assign requested address가 남았다면 원인은 명확하다. 바로 TCP TIME_WAIT 소켓 고갈이다. 우리의 대규모 API 게이트웨이 환경에서 초당 1,000개 이상의 연결이 쏟아질 때 6,700개까지 치솟던 TIME_WAIT 소켓을 단 300개 수준으로 95% 줄여낸 커널 튜닝 기법을 공유한다.
TIME_WAIT 소켓 고갈(Socket Exhaustion)은 클라이언트와 서버 간의 짧은 TCP 연결이 폭증할 때 가용 로컬 포트가 모두 소모되어 새로운 연결을 맺지 못하는 아키텍처 병목 현상이다. 이는 Linux 커널의 sysctl 파라미터 조정과 웹 서버의 연결 재사용 설정을 병행하여 해결해야 한다.
TIME_WAIT의 역설과 포트 고갈의 원리
Concept: 식당의 테이블 정리 시간 (TIME_WAIT)
손님이 식사를 마치고 떠난 뒤, 혹시나 두고 간 물건이 있을까 봐 테이블을 즉시 치우지 않고 일정 시간(약 60초) 비워두는 식당의 규칙을 상상해보자. 손님이 미친 듯이 몰려오는데 빈 테이블이 없어 새로운 손님을 돌려보내야 하는 상황이 바로 '소켓 고갈'이다.
TCP 프로토콜에서 연결을 먼저 끊는 쪽(Active Closer)은 반드시 TIME_WAIT 상태에 진입한다. 네트워크상에서 지연 도착하는 잉여 패킷(Delayed Segment)이 새로운 연결에 간섭하는 것을 막기 위한 필수적인 방어 기제다. 하지만 마이크로서비스(MSA) 아키텍처나 대규모 IoT 텔레메트리 환경처럼 짧은 연결(Short-lived Connection)이 폭우처럼 쏟아지는 서버에서는 이 안전장치가 곧 시스템의 숨통을 조인다. 포트 하나가 TIME_WAIT 상태에 빠지면 최대 60초 동안 재사용할 수 없기 때문에, 로컬 포트 범위(기본 약 28,000개)는 순식간에 고갈된다.
커널 파라미터(sysctl)와 Nginx 최적화 구현
본 튜닝은 최신 안정화 버전인 Nginx 1.26 이상 및 Linux Kernel 6.x 환경을 기준으로 작성되었다. 애플리케이션 계층과 OS 계층 양쪽의 한계치를 동시에 개방해야만 트래픽 병목을 타개할 수 있다.
# 1. TCP 연결 및 시스템 튜닝 (/etc/sysctl.conf)
# TIME_WAIT 소켓을 새로운 아웃바운드 연결에 재사용하도록 허용한다.
net.ipv4.tcp_tw_reuse = 1
# 소켓 대기열(Listen Queue)의 최대 크기를 확장하여 패킷 드롭을 방지한다.
net.core.somaxconn = 65535
# 가용 로컬 포트 범위를 최대치로 늘려 포트 고갈을 방어한다.
net.ipv4.ip_local_port_range = 1024 65535
# FIN-WAIT-2 상태의 소켓 대기 시간을 기본 60초에서 15초로 단축한다.
net.ipv4.tcp_fin_timeout = 15
# 시스템 전체의 최대 파일 디스크립터 한도를 상향한다.
fs.file-max = 2097152
위 설정을 sysctl -p 명령어로 적용한 뒤, Nginx에서 소켓을 효율적으로 다루도록 워커 프로세스와 백엔드 Keepalive 설정을 덮어씌운다.
# 2. Nginx 워커 및 Keepalive 튜닝 (/etc/nginx/nginx.conf)
worker_processes auto;
worker_rlimit_nofile 65535; # 커널의 file-max 한도에 맞춰 워커당 파일 디스크립터 상향
events {
worker_connections 16384; # 단일 워커가 처리할 수 있는 최대 연결 수
use epoll;
multi_accept on;
}
http {
# 클라이언트와의 연결 유지 시간
keepalive_timeout 65;
keepalive_requests 1000;
upstream backend {
server 127.0.0.1:8080;
# 백엔드 서버와의 내부 통신 연결을 유지한다. (TIME_WAIT 감소의 핵심)
keepalive 100;
}
server {
location / {
proxy_pass http://backend;
# HTTP 1.1을 사용하고 Connection 헤더를 비워야 keepalive가 동작한다.
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}
}
Watch out for: tcp_tw_recycle의 치명적 함정
과거의 많은 튜닝 가이드가 net.ipv4.tcp_tw_recycle = 1을 권장했다. 하지만 이 옵션은 NAT 환경(모바일 핫스팟, 사내 공유기 등)을 거쳐 들어오는 여러 클라이언트의 타임스탬프를 교란시켜 무작위 패킷 드롭을 유발한다. 이 치명적인 버그로 인해 Linux Kernel 4.12 버전부터 해당 파라미터는 커널에서 완전히 삭제되었다. 구형 시스템을 운영하더라도 절대 이 옵션을 켜서는 안 된다.
자주 묻는 질문 (FAQ)
Q. TIME_WAIT 상태의 소켓은 정확히 왜 생성되는가?
A. TCP 4-Way Handshake 과정에서 연결 종료를 먼저 요청한 측(Active Closer)에서 생성된다. 네트워크 지연으로 인해 늦게 도착하는 잉여 패킷을 안전하게 소멸시키고, 이전 연결의 잔여 패킷이 동일한 포트를 사용하는 새로운 연결에 간섭하지 않도록 보장하는 프로토콜의 표준 방어 기제다.
Q. Nginx에서 keepalive를 설정하면 TIME_WAIT 문제를 완벽히 해결할 수 있는가?
A. 완벽히 '제로(0)'로 만들 수는 없지만 발생 빈도를 극적으로 낮춘다. Nginx와 백엔드 간에 keepalive를 설정하면 1,000번의 독립적인 통신을 1번의 지속적인 TCP 연결로 처리하게 되므로, 매 요청마다 발생하던 TIME_WAIT 소켓 생성량이 1/1000 수준으로 감소한다.
Q. somaxconn 값을 높여도 Nginx에서 성능 변화가 없는 이유는 무엇인가?
A. 커널의 net.core.somaxconn 값을 상향하더라도 Nginx의 Listen Queue 기본 설정값이 낮게 잡혀있다면 소용이 없다. 시스템 전역의 한도를 높인 후, nginx.conf 내에서 워커 연결 수(worker_connections)와 파일 개수 제한(worker_rlimit_nofile)을 동기화하여 아키텍처 전체의 병목 구간을 일관되게 열어주어야 성능 향상이 일어난다.
Post a Comment