연결을 먼저 닫은 쪽이 마지막 ACK를 보낸 뒤 일정 시간 머무는 상태. 보통 2×MSL(Maximum Segment Lifetime, 패킷이 네트워크에 살아 있는 최대 시간) 동안 유지된다 — 리눅스에서 보통 60초.
왜 바로 끝내지 않는가 — 두 가지 이유
① 마지막 ACK가 유실될 수 있다
클라 ──── ACK ───X (유실)
서버 : ACK를 못 받았으니 FIN을 재전송
클라 : 이미 닫았다면? → 응답할 주체가 없어 서버는 계속 재전송하다 실패
TIME_WAIT로 남아 있어야 FIN 재전송을 받아 다시 ACK해 줄 수 있다.
② 늦게 도착한 패킷이 새 연결에 섞이는 것을 막는다
같은 포트 조합으로 새 연결이 곧바로 열리면, 이전 연결의 지연 패킷이 새 연결의 데이터로 오인될 수 있다. 2×MSL을 기다리면 옛 패킷은 네트워크에서 모두 사라진다.
왜 하필 2×MSL인가
MSL(가는 데 걸리는 최대 시간) + MSL(그에 대한 응답이 오는 최대 시간) = 왕복 최악.
이 시간이면 이전 연결의 어떤 패킷도 남아 있지 않다.
실무에서 문제가 되는 경우
짧은 연결을 대량으로 맺는 쪽에 TIME_WAIT 소켓이 쌓인다. 소켓은 4-튜플 (출발IP·출발포트·목적IP·목적포트)로 구분되는데, 클라이언트 포트는 3만~6만 개 정도라 포트 고갈이 일어날 수 있다.
ss -s # 상태별 소켓 수 확인
netstat -an | grep TIME_WAIT | wc -l
완화 방법
| 방법 | 내용 |
|---|---|
| 연결 재사용 | keep-alive·커넥션 풀 — 가장 근본적인 해결 |
SO_REUSEADDR | TIME_WAIT 상태의 주소를 재바인딩 허용 |
tcp_tw_reuse | 타임스탬프가 안전하면 재사용(클라 측) |
tcp_tw_recycle은 NAT 환경에서 연결이 끊기는 문제로 커널에서 제거됐다 —
옛 블로그를 보고 켜면 안 된다.
CLOSE_WAIT과 구분 — 면접 단골
| TIME_WAIT | CLOSE_WAIT | |
|---|---|---|
| 누구에게 | 먼저 닫은 쪽 | 나중에 닫는 쪽 |
| 성격 | 정상 절차 | 대개 애플리케이션 버그 |
| 해소 | 시간이 지나면 자동 | close() 를 불러야만 |