카테고리 없음

TCP 통신과 Keep-alive로 통신 효율 최적화: 실무에서 경험한 Connection Timeout 해결 사례

백엔드 유성 2023. 8. 14. 23:21

먼저 Keep-alive 에 앞서 TCP 통신부터 보겠습니다.

 

클라이언트와 서버의 TCP 통신을 하기 위해서는 여러 준비 과정이 필요합니다.

  • Three-way handshake
  • Slow Start
  • 데이터 압축 등

Three-way handshake란?

Three-way handshake는 TCP 통신을 시작하기 전에 클라이언트와 서버가 통신 가능한 상태인지 확인하는 절차입니다.

예를 들어, 구글의 메인 페이지에 접속한다고 생각해봅시다.

  1. 사용자가 브라우저에 www.google.com을 입력하고 엔터를 누르면 시작됩니다.
  2. 사용자(클라이언트)는 구글(서버)에게 sync 패킷을 전송합니다.
  3. 구글은 이를 수신하고 사용자에게 ack 패킷과 함께 자신의 sync 패킷을 전송합니다.
  4. 사용자는 마지막으로 ack 패킷을 구글에게 보내, Three-way handshake 과정을 완료합니다.
    (이 순간부터 사용자와 구글은 논리적으로 연결된 상태입니다.)
  5. 연결이 확립되면, 사용자는 구글에게 www.google.com 페이지의 내용을 요청합니다.
    (TFO 방식을 사용할 경우, 마지막 ack 패킷과 함께 HTTP 요청을 전송할 수 있습니다.)
  6. 이후 구글은 요청된 페이지의 내용을 사용자에게 전송합니다.

 

만약 같은 요청을 10번 반복한다면, 기본적으로는 Three-way handshake도 10번 반복됩니다.

 

이러한 반복적이고 복잡한 절차를 효율적으로 처리하기 위해 keep-alive TCP 메커니즘이 도입되었습니다.

keep-alive를 사용할 경우, Three-way handshake와 TCP Slow Start는 최초의 연결 설정 시에만 수행됩니다.

한 번의 연결 설정 후에는 10번의 요청에 대해 데이터만 송수신하게 되어, 전반적인 통신 효율이 크게 향상됩니다.

(TCP Slow Start는 오랜 시간 동안 데이터 전송이 없으면 초기 상태로 돌아갈 수 있습니다.)

 

최근에 실무에서 TB to TB 노드 간 통신에서 특별한 케이스를 경험했습니다. 이는 일반적인 접근법이 아니라 특정 상황에서의 연결 안정성을 위해 채택한 방식입니다.

(만약 개발 기간이 길었다면 DB/메모리 캐싱 방법을 사용했을 겁니다.)

 

2초의 응답시간을 가진 요청을 총 600번 실행했습니다. 이 작업은 20개의 멀티쓰레드와 5개의 Connection Pool을 활용했습니다.

서버의 리소스를 효율적으로 사용하고 통신 비용을 줄이기 위해 keep-alive 연결 방식을 선택했습니다.

처음에는 5개의 Connection Pool로 Three-way handshake를 시작했고, 이후 keep-alive를 통해 데이터 처리를 계속했습니다.


그러나 450번째 요청 이후부터 20번 연속으로 connection timeout 오류가 발생했습니다. 클라이언트 측의 설정은 30분 동안의 timeout을 허용했으나, 이는 서버측의 timeout 설정이 대략 5분정도로 설정이 되어있던 것으로 기억합니다.


만약 서버의 과부하 문제일 경우, 작업을 즉시 중단하고 근본적인 문제 해결 방법을 찾거나 서버측의 업스케일링을 요청해야 합니다. 저는 서버의 timeout 설정에 대한 문제로 Connection을 다시 열어주는 방식을 사용했습니다.

이 문제를 해결하기 위해 이미 Timeout으로 인한 연결 종료 후, Connection: close 헤더로 연결을 명시적으로 종료했고, 새로운 연결을 시작했습니다.

Retrofit2를 사용 중이었기에 OkHttpClient의 Interceptor를 활용해 Connection을 강제로 종료하고, 다시 http 요청을 보내서 해당 문제를 해결하였습니다.