0x0102

DevOps - TIME_WAIT 소켓이 서비스에 미치는 영향 본문

Study/DevOps

DevOps - TIME_WAIT 소켓이 서비스에 미치는 영향

jxx_yxjx 2024. 1. 29. 15:34

1. TCP 통신 과정

3 way handshake : 통신을 시작하기 전 최초의 연결을 맺는 과정
클라이언트 --- SYN(통신 시작 신호) --> 서버
서버 --- SYN + ACK --> 클라이언트
클라이언트 --ACK--> 서버
이후 클라이언트는 서버에 데이터를 요청
 
4 way handshake 
서버(연결을 끊으려고 하는 쪽) --FIN(종료 신호)--> 클라이언트
클라이언트 --- ACK --> 서버  // 클라이언트의 사용한 소켓 정리
클라이언트 --- FIN --> 서버
서버 --ACK--> 클라이언트 // 소켓 정리
 
 + tcpdump로 위와 같은 과정을 실제로 확인해 볼 수 있다.
 

2. TIME_WAIT  소켓의 문제점

위에서 서버가 연결을 끊으려고 했다면 서버는 active closer, 끊기는 쪽인 클라이언트는 passive closer라고 한다.
이러한 정의가 중요한 이유는 active closer 에 TIME_WAIT 소켓이 생성되기 때문이다. 
+) 서버에서 TIME_WAIT 소켓이 몇 개나 있는지 확인하는 방법으로는 netstat 명령이 있다.
 
TIME_WAIT 소켓이 많아지면 나타나는 문제점
1) 로컬 포트 고갈에 따른 애플리케이션 타임아웃
커널을 프로세스가 외부와 통신하기 위해 소켓의 생성을 요청한다. 이 때 모든 로컬 포트가 TIME_WAIT 상태라면 할당할 수 있는 로컬포트가 없기에 외부와 통신을 하지 못하고 타임아웃이 발생한다.
 
2) 잦은 TCP 연결 맺고 끊음에 의한 서비스 응답 속도 저하
TIME_WAIT 소켓 자체가 연결을 끊기 때문에 발생하는 것인데, 해당 상황이 잦다면 그만큼 잦은 3 way handshake가 필요하고 이는 전체적인 서비스 응답 속도 저하를 야기한다.
+) 이런 현상을 막기 위해 대부분의 애플리케이션에서는 connection pool 방식으로 한번 맺은 tcp 연결을 재사용하게 구현한다.
 

3. 클라이언트에서의 TIME_WAIT

HTTP 기반의 서비스는 대부분 서버가 연결을 끊는 경우가 많기에 서버에서 TIME_WAIT가 생긴다고 생각할 수 있지만 서비스를 제공하는 서버는 연동하는 시스템에 대해 클라이언트가 될 수 있다.
즉, 통신하는 과정에 따라 서버의 역할을 했던 서버가 클라이언트의 역할이 되기도 한다는 것이다.
 
클라이언트 입장에서 TIME_WAIT의 가장 큰 문제는 로컬 포트가 고갈되는 것이다.
소켓이 acitve close 되면 TIME_WAIT 상태가 되고, 해당 소켓은 TIME_WAIT이 풀리기 전까지 사용할 수 없다.
이렇게 다량의 포트가 쌓여서 사용할 수 없어지면 로컬 포트가 고갈되어 서버와 통신할 수 없게 된다.
 

4. net.ipv4.tcp_tw_reuse

위와 같은 문제가 발생했을 때 대응 방법으로 커널 파라미터를 이용할 수 있다.
net.ipv4.tcp_tw_reuse 는 외부로 요청할 때 TIME_WAIT 소켓을 재사용하게 해준다.

1 - 상단 파라미터 범위 안에서 임의의 값 선택

2- TW상태의 소켓 중 이미 사용 중인 값인지 확인

좌측 3 : 사용 중이며 tw_reuse가 꺼져있으면 1번 과정 다시 진행

하단 3: 사용 중이며 tw_reuse가 켜져있으면 바로 할당


 
동작과정
- kernel -> 파라미터 범위 안에서 임의 값을 선택한 후 TW Socker array에 해당 값을 사용하는 동일 쌍 소켓이 있는지 확인
- 파라미터 값이 켜져 있으면 -> 해당 값 사용하도록 그대로 리턴 
- 파라미터 값이 꺼져있으면 -> 다른 값은 선택해서 확인하는 과정 다시 진행
 

5. Connection Pool 

4번에서 파라미터로 소켓을 재사용할 수 있다는 것을 확인했으나 근본적인 해결 방법은 아니다.
TIME_WAIT 소켓이 쌓이는 문제는 acitve close 때문에 발생한다. 그럼 연결을 먼저 끊지 않으면 어떨까?
 
클라이언트의 동작방식은 크게 두가지인데
1) Connection less -> HTTP가 많이 사용하는 방식으로 요청마다 소켓을 새로 연결
2) Connection pool -> 미리 소켓을 열어놓고 요청 처리
2번 방식을 사용하면 불필요한 연결과 해제 과정이 없어 응답 속도에서 더 뛰어나다.
 
Connection Pool은 로컬 포트의 무분별한 사용도 막고 서비스의 응답 속도도 향상시킬 수 있기 때문에 사용하는 것이 좋다. +) 그러나 단점도 존재한다. 이후 살펴볼 예정

 

6. 서버 입장에서의 TIME_WAIT 

서버는 소켓을 열고 요청을 받는 입장이라 로컬 포트 고갈과 같은 문제는 발생하지 않는다.
그러나 마찬가지로 불필요한 연결과 해제 과정이 반복된다.
서버 입장에서 TIME_WAIT 소켓을 줄일 수 있는 방법은 무엇일까?
 

7. net_ipv4.tcp_tw_recycle

4번과 이름이 유사하지만 전혀 다른 동작 로직의 파라미터이다.
4번에서 설명한 파라미터가 나갈 때 사용하는 로컬 포트에서 TIME_WAIT 소켓을 재사용하는 파라미터라면
해당 파라미터는 반대로 서버 입장에서 TIME_WAIT 상태의 소켓을 빠르게 회수하고 재활용하는 파라미터이다.

1- FIN 패킷 발송을 통해 active close한 것을 커널에 알림

2- 해당 소켓 정보를 TW socket array에 저장

3 - 해당 소켓으로부터 받은 timestamp 값 저장


 
동작 과정
tw_recycle이 켜지면 커널은 1) 가장 마지막 소켓으로부터 들어온 timestamp 저장 2) TIME_WAIT 소켓 타이머를 RTO 기반 값으로 변경. 두가지 일을 추가로 진행한다.
이 때 RTO가 보통 ms 단위이므로 소켓이 굉장히 빨리 사라진다. 그러나 1번 과정 때문에 서비스에 문제가 생길 수 있다.
서로 다른 클라이언트라고 해도 같은 NAT를 사용한다면 서버입장에서는 같은 IP로 보일텐데,
같은 IP에서 클라이언트들의 요청에 대해 timestamp의 값이 더 작은 클라이언트가 후에 요청이 온다면 잘못된 요청으로 판단하고 패킷을 버리기 때문이다.
이렇게 패킷이 드랍되는 현상이 클라이언트의 요청을 직접 받는 웹 서버에서 주로 발생할 수 있으므로 웹 서버에서는 절대로 tw_recycle을 켜면 안 된다.
 

8. keepalive 

keepalive는 한번 맺은 세션을 요청이 끝나도 유지해주는 기능이다.
예를 들어 2초 간격으로 get 요청이 계속 들어온다면 2초마다 세션을 맺기 보다 하나의 세션을 연결해놓고 그 연결을 유지하며 지속적으로 요청을 처리하는 것이 서버 리소스 활용 측면에서 도움이 된다.
서버 입장에서 keepalive를 켜서 세션을 유지해주면 TIME_WAIT 소켓을 줄일 수 잇으며 불필요한 TCP 연결 및 해제 과정이 사라져서 서비스 응답 속도를 향상시킬 수 있다.
 

9. TIME_WAIT 상태의 존재 이유

TIME_WAIT 소켓의 핵심은 연결이 종료된 후에도 소켓을 바로 정리하지 않고 일종의 연결 종료에 대한 흔적을 남겨 놓는 것에 있다.
이러한 점을 이용하여 패킷 유실에 따른 비정상적인 통신 흐름의 발생을 방지할 수 있다.
즉, TIME_WAIT으로 패킷 유실로 인해 발생한 FIN과 ACK의 재전송을 처리할 수 있는 기회를 얻는 것이다.
이러한 이유로 TCP에서는 연결을 끊은 이후에도 일정 시간 소켓을 유지하며 그 상태를 TIME_WAIT 상태로 정리하였다.
 

10. Case study - nginx upstream에서 발생하는 TIME_WAIT

자바 기반으로 서비스를 개발하면 웹 서버로 tomcat 이나 netty를 사용하게 된다. // 앱서버
그리고 이런 서버를 직접 트래픽을 받게 하지 않고 nginx나 apache로 처리하도록 구성하는 경우가 많다. // 웹서버
이 때 두 서버 간에 keepalive를 적용하지 않으면 TIME_WAIT이 발생한다.
 
이 경우 두 가지 문제가 발생한다.
1) 웹 서버가 사용할 로컬 포트 부족으로 로컬 포트 고갈 (그러나 tw_reuse로 해결 가능)
2) 웹 서버에서 앱 서버로 보내는 모든 요청에 연결 과정이 일어나므로 성능 낭비와 서비스 응답 속도 지연
 
그렇다면 왜 앱서버와 웹서버를 구분할까? 
다음과 같은 점이 고려되기 때문이다.
1) HTTP 사용시 인증서 설정과 관리 문제
2) UserAgent 확인, Referer 확인 등 ..
3) 코드 구현 및 개발 생산성