Time_WAIT Socket 이란 ?

Jisu·2025년 5월 1일

배경

회사에서 istio를 도입하면서 tcp connection 관련 트러블 슈팅을 해야하는 일이 많아졌다. 대표적인것이 pod gracefulshutdown에 따라 istio proxy container 종료전에 남아있는 connection을 추적하는 일이었는데, 이 과정에서 CS 적인 지식.. 특히 네트워크 부분에 대한 깊이 있는 이해가 필요했다. 그 와중에 다시 접한 개념이 옛날에 어렴풋이 봤던 TIME_WAIT Socket이라는 개념으로 복습을 위해 정리한다.


TCP 연결에서 TIME_WAIT

TCP는 연결을 종료할 때 4-way handshake를 사용하게 된다. 클라이언트 - 서버 통신모델에서 양쪽 모두에게 통신 종료를 알리고 확인하는 단계를 포함한다.

[클라이언트]                     [서버]
ESTABLISHED                    ESTABLISHED
    |                              |
    | ----- FIN (seq=x) -------->  |
FIN_WAIT_1                         |
    |                              |
    |  <---- ACK (seq=y, ack=x+1)  |
FIN_WAIT_2                    CLOSE_WAIT
    |                              |
    |                     (남은 데이터 전송 완료)
    |                              |
    |  <---- FIN (seq=y) -------   |
    |                        LAST_ACK
    |                              |
    | ----- ACK (ack=y+1) ------>  |
TIME_WAIT                     CLOSED
    |                              |
(2MSL 타임아웃)                     |
    |                              |
CLOSED                             |

TCP 연결 종료할 때는 먼저 종료를 선언하는 쪽이 주체가 되며 클라이언트와 서버 모두가 주체가 될 수 있다. 여기서 TIME_WAIT은 TCP 연결을 종료하는 주체가 일정 시간 동안 그 연결 정보를 유지하는 소켓의 상태를 의미한다.

TIME_WAIT 상태는 기본적으로 2 * MSL(Maximum Segment Lifetime) 동안 유지되며 Linux에서는 /proc/sys/net/ipv4/tcp_fin_timeout 등으로 조절할 수 있다고 한다.


TIME_WAIT가 필요한 이유

크게 2가지가 있다.

1. 지연 패킷 충돌 방지

이전 연결에서 네트워크 어딘가에 남아있는 지연된 패킷이 새로운 연결에 영향을 주지 않도록 하기 위함.

한 마디로 늦게 들어오는 패킷이 있을까봐 연결 정보를 널널하게 유지하는 것이다.

2. 정상 종료를 보장

위 TCP 연결 그림에서 FIN → ACK 과정에 ACK이 유실되면 FIN은 재전송된다. TCP는 신뢰성있는 프로토콜로 재전송 메커니즘이 있기 때문이다!

대표적으로는 이런 상황이다.
상대방: FIN 보냄
나: ACK 보냄 + TIME_WAIT 진입

이때 ACK이 유실된다면
상대방은 FIN을 재전송한다.
하지만 나는 TIME_WAIT 상태이기 때문에 이전 연결 정보를 유지하고있으므로 다시 ACK 응답이 가능하다.


TIME_WAIT 소켓의 단점과 HTTP Keep Alive

"소켓은 네트워크 통신을 위한 엔드포인트로, 운영체제에서 IP 주소와 포트 번호의 조합으로 식별됩니다. 하나의 소켓은 특정 프로토콜(TCP/UDP)을 사용하여 특정 IP 주소와 포트 번호의 조합을 통해 통신합니다."

TIME_WAIT 소켓이 생성되면 결국 os 단에서 하나의 포트를 점유하고 있는 상태가 되는 것이다. 그런데 하나의 애플리케이션이 생성할 수 있는 소켓의 양은 한정적이므로, TIME_WAIT 소켓이 많아지면 신규 연결을 할 수가 없다...

HTTP는 TCP 기반이며 HTTP 클라이언트가 매 요청마다 새로운 TCP 연결을 열고 닫게 되며 이러한 문제를 직격으로 맞게 된다.. 특히 API 서버나 프론트엔드 백엔드 간 통신에서 이런 방식이 누적되면 수천 개의 TIME_WAIT 상태가 쌓이는 걸 보게 된다.

이를 줄이는 가장 효과적인 방법 중 하나가 바로 HTTP Keep-Alive이다. HTTP 1.1부터 기본 동작인 Connection 재사용 방식으로, 하나의 TCP 연결 위에서 여러 HTTP 요청을 연달아 보내는 방식이다.

예를 들어 기존에는

요청 1 → TCP 연결 열기 → 요청 → 응답 → 연결 닫기 (TIME_WAIT 생성)
요청 2 → 다시 TCP 연결 열기 → 요청 → 응답 → 연결 닫기 (TIME_WAIT 생성)
...

이랬다면 keep-alive 도입 이후에는

TCP 연결 열기
요청 1 → 응답
요청 2 → 응답
요청 3 → 응답
...
일정 시간 후 연결 닫기 (TIME_WAIT 1개만 생성)

이를 통해

TIME_WAIT 소켓 수 급감
연결 생성/해제 비용 감소로 성능 개선
클라이언트/서버 모두 리소스 절약

를 얻을 수 있다.


DB Connection Pool

Connection Pool(커넥션 풀)이란, 자주 쓰이는 네트워크 연결(TCP, DB 등)을 미리 만들어두고 재사용하는 기법이다

DB 서버와 클라이언트는 TCP/IP 위에서 동작하며, 각 DB는 자체 프로토콜을 정의하여 요청/응답을 주고 받는다.

DBMS프로토콜 종류특징
MySQL / MariaDBMySQL Native ProtocolTCP 기반, 바이너리 프로토콜
PostgreSQLPostgreSQL Protocol자체 TCP 바이너리 프로토콜
MongoDBMongoDB Wire ProtocolTCP + BSON 기반
RedisRESP (REdis Serialization Protocol)텍스트 기반이지만 매우 간단하고 빠름
CassandraCQL Binary ProtocolTCP 기반, CQL 바이너리
ElasticsearchHTTP + JSON예외적으로 HTTP 사용 (RESTful)
ClickHouseNative TCP Protocol, HTTP도 지원binary + optional HTTP
SQLite로컬 파일 기반네트워크 없음 (예외)

그래서 매 요청마다 tcp 연결을 맺으면 Handshake 시간도 소요될 뿐더러 TIME_WAIT 누적되고...DB 서버 부하도 증가한다.

그래서 다음과 같은 방식으로 connection pool을 관리한다.

서버 시작 시 DB 연결 10개 미리 생성
↓
요청이 오면 → 기존 연결 할당 → 쿼리 실행 → 다시 풀에 반납
↓
요청 끝나도 연결은 살아있음 (Keep-Alive)

TCP 및 DB handshake 비용 제거

재사용으로 TIME_WAIT 줄어듦

최대 동시 연결 수 제어 가능 (풀 크기)

profile
기술 공유를 즐기는 Engineer 장지수입니다!

0개의 댓글