[IT 심층 분석]
고트래픽 환경에서 TCP TIME_WAIT 소켓 폭증으로 인한 포트 고갈 현상 추적 및 해결 완전 정복
김민준 · IT 시스템 엔지니어|
인터넷 트래픽 피크 타임에 서비스 전면 장애가 발생했습니다. 사용자 요청 수는 평소와 다름없이 정상적이었으나 특정 서버 군에서 신규 TCP 연결 수립이 아예 불가능해지는 현상이 발생한 것입니다. 서버 콘솔에 접속해서 ss -s 명령어로 소켓 상태를 들여다보았을 때 눈에 들어온 숫자는 충격 그 자체였습니다. TIME_WAIT 상태에 머물고 있는 소켓의 수가 무려 6만 개를 상회하고 있었고 이것이 사용 가능한 임시 포트(Ephemeral Port) 번호를 완전히 잠식해버린 것이 원인이었습니다. 커널이 허락하는 포트 범위가 제한되어 있는데 TIME_WAIT 소켓들이 기본 설정된 60초 동안이나 좀비처럼 포트를 쥐고 놓아주지 않으니 새로운 연결을 맺기 위한 포트가 바닥나버린 것입니다.
TIME_WAIT 상태는 TCP 프로토콜이 안정적인 종단 간 연결 해제를 보장하기 위해 설계가 된 정상적인 상태입니다. 연결을 먼저 끊는 측이 상대방이 마지막 ACK를 수신했는지 확인하기 위해 잠시 대기하는 것인데, 이 대기 시간이 짧은 HTTP 요청을 쉴 새 없이 주고받는 현대 고빈도 서비스 환경에서는 폭탄이 될 수 있습니다. 매 요청마다 소켓을 만들고 버리는 단기 연결 패턴이 반복되면 TIME_WAIT 소켓이 우후죽순처럼 신규 시간에 생겨나는 속도가, 기존 소켓이 만료되어 사라지는 속도를 아득히 초월해버리는 임계점이 찾아오게 됩니다.
이 문제를 다층적으로 해결하기 위해 커널 파라미터 튜닝과 애플리케이션 아키텍처 개선을 동시에 진행했습니다. 먼저 sysctl 설정을 통해 net.ipv4.tcp_tw_reuse 옵션을 1로 활성화했습니다. 이 설정은 일정 조건이 충족되면 TIME_WAIT 상태의 기존 소켓을 새로운 연결에 재활용해도 된다고 커널에게 허락하는 것입니다. 이와 병행해 net.ipv4.ip_local_port_range를 기본 범위인 30000~60000에서 1024~65535로 대폭 확장하여 이론적으로 사용 가능한 포트 번호 자체의 풀을 몇 배 이상 늘렸습니다.
그러나 파라미터 튜닝만으로는 근본 처방이 될 수 없음을 알고 있었습니다. 단기 연결 생성 자체를 줄이는 것이 궁극의 해법이기 때문입니다. 서버 간 통신을 담당하는 내부 HTTP 클라이언트 라이브러리의 설정을 뜯어보니 Connection: close 헤더가 모든 요청에 붙어나가면서 사실상 매 요청마다 소켓을 낭비하고 있었습니다. 이를 HTTP Keep-Alive 방식으로 전환하고 연결 풀링 라이브러리를 적용해 재사용 가능한 커넥션을 풀에서 꺼내쓰도록 아키텍처를 교체했습니다. 변경 배포 직후 TIME_WAIT 소켓 수는 수천 개 수준으로 급락했고 포트 고갈 현상은 이후 단 한 번도 재발하지 않았습니다. 네트워크 프로토콜의 동작 원리를 소홀히 하는 것이 얼마나 쉽게 전면 장애로 이어지는지를 생생하게 체험한 사건이었습니다.