목차
TCP 연결 생명주기와 TIME_WAIT의 위치
TCP 연결은 대략 다음 순서로 상태가 바뀐다.
SYN
SYN SENT와 SYN RECV
ESTABLISHED
FIN WAIT와 CLOSE WAIT
TIME_WAIT
CLOSED
이 중 TIME_WAIT은 연결을 먼저 종료한 쪽, 즉 능동적으로 FIN을 보낸 쪽이 일정 시간 동안 머무는 상태다. 보통 최대 세그먼트 수명 두 배 정도의 시간 동안 유지되며, 이 시간 동안 지연되어 도착하는 패킷을 안전하게 무시하고 포트 재사용에 따른 혼선을 막는 역할을 한다.
다시 말해 TIME_WAIT이 많은 자체는 거의 항상 정상 동작이다. 문제는 그 수가 과도하게 많아져서 커널 리소스를 잡아 먹거나 새로운 연결을 받을 여유 포트가 부족해지는 경우다.
특정 포트 기준으로 보는 이유
운영 환경에서는 보통 다음과 같은 식으로 현상을 접하게 된다.
- 특정 웹 서버 포트에서만 TIME_WAIT이 수만 개 쌓인다
- 데이터베이스 클라이언트 포트에서 TIME_WAIT이 급증한다
- 로드밸런서 뒷단 서버의 특정 포트가 TIME_WAIT 상태로 가득 차 있다
결국 TIME_WAIT은 포트와 한 쌍으로 붙어 있기 때문에, 문제를 분석할 때도 포트 기준으로 바라보는 것이 직관적이다.
TIME_WAIT이 많은 전형적인 원인
그렇다면 왜 어떤 포트는 멀쩡한데 특정 포트만 TIME_WAIT이 비정상적으로 많아질까. 실무에서 자주 마주치는 원인을 유형별로 정리해 보자.
짧은 수명의 연결을 너무 자주 여닫는 패턴
가장 흔한 원인은 애플리케이션이 매우 짧은 시간에 많은 연결을 열고 바로 닫는 패턴이다.
대표적인 예는 다음과 같다.
- HTTP keep alive를 끄고 매 요청마다 새로 연결을 여는 웹 클라이언트
- 배치 작업이나 스크립트가 DB 쿼리 한 번에 연결을 열었다가 바로 닫는 경우
- 백엔드 서비스 간 RPC 호출이 모두 단발성 연결에 의존하는 구조
이 경우 능동적으로 연결을 닫는 쪽의 포트에 TIME_WAIT이 집중된다. 예를 들어 웹 서버가 클라이언트와의 연결을 먼저 종료한다면 서버 측 포트에 TIME_WAIT이 쌓인다. 반대로 내부에서 DB로 접속하는 애플리케이션이 연결을 빨리 끊는다면 애플리케이션 측의 출발 포트들에 TIME_WAIT이 다량으로 보이게 된다.
클라이언트 포트 재사용 범위 부족
클라이언트 역할을 하는 프로세스는 보통 임시 포트 영역을 사용한다. 이 영역이 좁게 잡혀 있거나 TIME_WAIT 상태가 너무 오래 남아 있으면 다음 같은 문제가 생긴다.
- 사용할 수 있는 임시 포트가 바닥나 새 연결 생성이 실패한다
- netstat이나 ss로 보면 특정 포트 범위에 TIME_WAIT이 빽빽하게 들어차 있다
이는 시스템 전역 임시 포트 범위 설정과 커널의 포트 재사용 전략에 영향을 받는다. 그런데 겉으로는 특정 서비스의 포트에서만 문제가 나타나는 것처럼 느껴져 분석을 헷갈리게 만든다.
로드밸런서나 프록시가 많은 TIME_WAIT을 떠안는 구조
대부분의 로드밸런서나 리버스 프록시는 클라이언트와 백엔드 서버 사이에서 연결을 중계하는 역할을 한다. 이때
- 클라이언트와의 연결
- 백엔드와의 연결
이 각각 독립적인 TCP 연결로 존재한다. 어느 쪽을 먼저 끊느냐에 따라 TIME_WAIT이 라우터나 로드밸런서 포트에 집중될 수 있다.
예를 들어 프록시가 백엔드 서버와의 연결을 매 요청마다 새로 열고 닫는다면, 백엔드 서버의 수신 포트와 프록시의 임시 포트 범위에 TIME_WAIT이 쌓이는 식이다.
비정상적인 예외 상황이나 재시도 폭주
정상 상황이 아니더라도 TIME_WAIT이 늘어나는 패턴이 있다.
- 타임아웃이 빈번하게 발생해 연결을 강제로 정리하는 경우
- 애플리케이션이 연결 실패 시 과도하게 빠른 재시도를 반복하는 경우
- 네트워크 오류로 인해 연결이 반쯤 열린 상태에서 자주 끊기는 경우
이때도 결국 결과적으로는 많은 연결이 짧은 시간 내에 종료되면서 TIME_WAIT 상태가 폭증한다. 로그와 메트릭을 같이 보지 않으면 단순히 TIME_WAIT 숫자만 보고는 원인을 찾기 어렵다.
TIME_WAIT이 많은지 확인하는 방법
원인을 추적하려면 먼저 현상을 숫자로 확인해야 한다. 특정 포트 기준으로 TIME_WAIT을 세어 보는 것이 출발점이다.
ss와 netstat로 특정 포트 확인
요즘 배포판에서는 ss 명령이 기본이다. 다음과 같이 사용할 수 있다.
ss -ant state time-wait
특정 포트에 집중된 것을 보고 싶다면
ss -ant state time-wait | grep '포트번호'
형태로 필터링하면 된다. netstat을 사용하는 환경이라면 비슷한 방식으로
netstat -ant | grep TIME_WAIT
명령으로 전체 목록을 보고, 다시 포트 기준으로 정렬하거나 카운트해 볼 수 있다.
어느 쪽이 능동 종료를 했는지 추론
정확한 책임 주체를 찾으려면 어느 방향에서 연결 종료를 먼저 요청했는지가 중요하다. 일반적으로 TIME_WAIT은 능동적으로 연결을 종료한 쪽에서 나타나므로, 다음 정보를 함께 보면 도움이 된다.
- 서버 포트가 고정값이고 클라이언트 포트가 넓은 임시 범위라면
서버의 수신 포트에 TIME_WAIT이 많다면 서버가 먼저 종료하는 패턴일 가능성이 높다 - 반대로 클라이언트 임시 포트 쪽에서 TIME_WAIT이 몰려 있다면
클라이언트가 먼저 연결을 끊는 구조일 가능성이 크다
여기에 애플리케이션 로그와 연결 수 통계를 함께 맞춰 보면 어느 쪽 코드에서 연결 라이프사이클을 조정해야 하는지 방향이 잡힌다.
TIME_WAIT이 많을 때 발생하는 부작용
TIME_WAIT 자체는 정상적인 상태지만, 수가 과도해지면 여러 부작용을 낳는다.
커널 리소스와 메모리 소모
각 TIME_WAIT 소켓은 커널의 메모리와 자료구조 자원을 조금씩 점유한다. 수십 개 수준에서는 아무 문제가 없지만
- 수만 개 이상
- 심하면 수십만 개 이상
쌓이기 시작하면 다른 소켓 처리에도 지연이 생기고 커널 메모리 부족 경고가 나타날 수 있다. 특히 자원이 제한된 컨테이너 환경에서는 이 영향이 더 크게 체감된다.
사용 가능한 포트 고갈
임시 포트 범위 내에서 TIME_WAIT 소켓이 포트를 오래 점유하면 재사용 가능한 포트가 줄어든다. 이때 새 연결을 열려고 하면 다음과 같은 오류가 발생할 수 있다.
- 연결 시도 실패
- 클라이언트에서 일시적 오류 메시지
- 애플리케이션에서 포트 부족 관련 예외
결국 특정 포트에서 TIME_WAIT이 많은 현상 뒤에는 전체 포트 자원이 소진되어 가는 위험이 숨어 있을 수 있다.
TIME_WAIT 원인 분석 실전 절차
실전을 기준으로 특정 포트의 TIME_WAIT 문제를 분석하는 흐름을 정리해 보자.
첫 단계 현상 수집
- ss나 netstat으로 TIME_WAIT 개수를 포트별로 센다
- 평소와 비교해 어느 정도가 비정상인지 기준을 만든다
- 애플리케이션 레벨에서 초당 연결 수와 요청 수를 함께 확인한다
이 단계에서 특정 포트 하나만 유난히 높다면 해당 포트의 역할과 애플리케이션 패턴을 떠올려 본다.
둘째 단계 연결 패턴 파악
- 해당 포트를 사용하는 프로세스를 ps와 lsof로 확인한다
- 애플리케이션 코드에서 연결 생성과 종료 지점을 살펴본다
- keep alive 설정과 커넥션 풀 사용 여부를 점검한다
웹 서비스라면
- 서버 측과 클라이언트 측 각각의 keep alive 설정
- 프록시와 로드밸런서의 커넥션 재사용 정책
을 나란히 비교하는 것이 좋다.
셋째 단계 재현과 모니터링
원인이 어느 정도 추정되면 테스트 환경에서 비슷한 요청 패턴을 만들어 TIME_WAIT 수가 어떻게 변하는지 본다.
- 단발성 연결로 부하를 줄 때
- keep alive를 켜고 일정 횟수까지 재사용할 때
- 커넥션 풀 크기를 조정할 때
각각 TIME_WAIT 그래프가 어떻게 움직이는지 관찰하면, 실제로 어느 튜닝 포인트가 효과가 있는지 확인할 수 있다.
TIME_WAIT을 줄이기 위한 개선 전략
원인을 파악했다면 이제는 어떻게 줄일 것인지 고민해야 한다. 무조건 커널 튜닝부터 하는 것보다 애플리케이션 구조와 연결 패턴을 먼저 고치는 것이 장기적으로 안정적이다.
HTTP keep alive와 커넥션 풀 적극 활용
웹 기반 서비스라면 가장 효과적인 방법은 단연 연결 재사용이다.
- 클라이언트와 서버 모두 keep alive를 활성화
- 프록시와 로드밸런서의 연결 유지 시간을 적절히 늘리기
- 데이터베이스나 메시지 브로커에 접속하는 클라이언트는 커넥션 풀을 사용
이렇게 하면 단위 시간 동안 생성되는 새 연결 수 자체가 줄어들어 TIME_WAIT의 총량도 자연스럽게 감소한다.
재시도 정책과 타임아웃 정교하게 조정
네트워크 오류나 타임아웃을 겪을 때 재시도가 과도하면 TIME_WAIT이 폭발적으로 증가할 수 있다. 다음 사항을 점검해 보자.
- 동일 대상에 대한 재시도 횟수와 간격이 너무 공격적이지 않은지
- 서버 타임아웃과 클라이언트 타임아웃이 어긋나 연결이 중간에 잘리고 있지 않은지
- 네트워크 장비나 방화벽이 세션을 너무 빠르게 정리하고 있지 않은지
적당한 타임아웃과 재시도 정책을 맞춰 주면 불필요하게 반쯤 열린 연결이 자주 끊기는 상황을 줄일 수 있다.
커널 파라미터 조정은 신중하게
일부 커널 파라미터는 TIME_WAIT 재사용에 직접 영향을 준다. 예를 들어
- TIME_WAIT 유지 시간 단축
- 재사용 가능한 경우 포트 재활용 허용
과 같은 옵션이 있다. 다만 이런 설정을 무턱대고 건드리면 지연된 패킷이 뒤늦게 도착했을 때 이전 연결과 새로운 연결이 혼동되는 위험을 키울 수 있다.
따라서 커널 튜닝은 다음 순서로 접근하는 편이 안전하다.
- 먼저 애플리케이션의 연결 패턴과 재사용 전략을 정비한다
- 테스트 환경에서 커널 파라미터를 조금씩 조정하며 안정성을 검증한다
- 모니터링 지표와 로그를 통해 부작용 여부를 충분히 확인한 뒤 운영에 적용한다
마무리 정리
특정 포트에 TIME_WAIT 상태가 많은 현상은 단순히 숫자를 줄이는 문제가 아니다. 그 이면에는 애플리케이션의 연결 패턴과 재시도 정책, 로드밸런서 구조와 커널 자원 관리 방식이 모두 얽혀 있다.
TIME_WAIT의 역할을 정확히 이해하고
- 어느 쪽이 능동적으로 연결을 끊는지
- 초당 새 연결 생성량이 어느 정도인지
- 포트 범위와 커널 자원 상황이 어떤지
를 차근차근 확인해 나가면 원인을 훨씬 명확하게 좁혀 갈 수 있다. 결국 TIME_WAIT 문제를 잘 다루는 것은 리눅스 네트워크와 애플리케이션 구조를 깊이 이해하는 지름길이기도 하다.