ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Linux TCP 재전송과 타임 아웃
    커널(Kernel) 2022. 12. 18. 00:54

     

    환경 정보

    OS: ubuntu18.04 

     

    TCP 재전송

    TCP는 신뢰성 있는 연결이기 때문에 데이터를 확실하게 주고받아야 한다.

    sender은 receiver에게 요청을 보낸 후 receiver으로부터 ACK를 기다리는데,

    ACK를 받지 못하게 된다면 패킷이 중간에 손실되었다고 판단하고 요청을 재전송한다. 

    이렇게 요청을 재전송하는 것을 TCP 재전송이라 한다. 

     

     

    RTO(Retransmission timeout)

    RTO는 재전송을 하기까지 ACK를 얼마나 기다려야 하는지를 나타내는 값이다. 

    sender는 RTO안에 ACK를 받지 못하면 재전송을 한다. 

     

    그리고 RTO에는 일반적인 RTOInit RTO가 있는데 일반적인 RTO는 RTT(Round Trip Time)를 기준으로 결정된다.

    예를 들어, RTT가 1초라면 최소 RTO가 1초 이상이여야 재전송을 할 수 있다. 

     

    Init RTO는 TCP Handshake가 일어나는 첫 번째 SYN 패킷에 대한 RTO를 의미한다. 

    전송이 일어나기 전에는 RTT를 알 수 없기 때문에 RTO는 임의로 설정한 값을 사용하며, 이를 Init RTO라 한다. 

    리눅스에서는 Init RTO가 1초로 구현되어 있다.  

     

    RTO는 ss 커맨드를 통해서 현재 세션에 설정되어 있는 RTO값을 확인할 수 있다.  

     

    첫번째 보이는 세션에 대해서는 RTO가 204m로, 204ms 동안 ACK를 받지 못하면 재전송한다. 

     

    참고로 재전송을 했지만 ACK를 받지 못하는 경우, 재전송과 관련된 커널 파라미터의 값만큼 재전송을 하게 된다.

    이때 재전송의 주기는 두 배씩 늘어난다. 

     

     

    재전송과 관련된 커널 파라미터 

    net.ipv4.tcp_syn_retries: TCP Handshake 과정에서 일어난 재전송에 대한 값으로 SYN 패킷에 대한 재시도 횟수다. 

     

    net.ipv4.tcp_synack_retries: TCP Handshake 과정에서 sender가 보낸 SYN에 대해서 SYN + ACK 패킷을 재전송하는 횟수다. 즉 Receiver 입장에서의 재전송 횟수다. 

     

    net.ipv4.tcp_orphan_retries: orphan socket에 대한 재전송 횟수다.

    orphan socket이란 TCP 연결을 끊을 때, FIN 패킷을 보낸 후, FIN_WAIT1으로 남는 소켓을 말한다. 

    FIN_WAIT1 상태에서 지정된 재전송 횟수까지 모두 보내고 나면 해당 소켓은 회수된다. 

    이 값이 너무 작으면 상대편의 소켓이 정리되기 전에 해당 소켓이 회수되어 문제가 있을 수 있다. 

    그래서  기본 TIME_WAIT의 유지시간이 60초 정도가 될 수 있도록 7 정도로 설정하는 게 좋다. 

    참고로 이 값은 0으로 설정되어 있어도, 여러 번 보내게 된다. 

     

    net.ipv4.tcp_retries1: 재전송시 네트워크가 잘못되었는지 확인하는 기준이다. 

     

    net.ipv4.tcp_retries2: 재전송시 더이상 통신할 수 없다고 판단하는 기준이다. 

     

     

    RTO의 최솟값 변경

    커널의 소스코드를 보면 RTO_MIN이 200ms으로 정의되어 있기 때문에 RTT가 아무리 작아도 RTO는 200ms 이하가 될 수 없다. 

     

    그리고 RTO는 RTT를 기반으로 계산되기 때문에 대부분이 200ms보다 커진다. 

     

    예를 들어 위 이미지에서 두 번째를 보면 RTO가 208ms, RTT는 5.193ms이다. 

    (RTT 뒤의 값은 편차인데, 편하게 생각하기 위해서 편차를 제외하고 보자)

    패킷을 주고 받는데 5.193ms 정도가 걸린다고 했을 때, 208ms는 재전송을 보내기에는 너무 길다.  

    주고받는데 5ms가 걸리는 종단 사이에 100ms가 지나도 응답을 받지 못한다면 이는 사실 유실된 것과 마찬가지기 때문에

    사실상 의미 없는 시간을 기다리는 것과 같다. 

     

    또한 서버 내부에서 통신하는 경우, 200ms는 긴 시간일 수 있다. 

    우리는 rto_min을 변경해서 효율적인 rto를 찾아 서비스 품질을 높여야 한다.  

     

    그렇다면 RTO_MIN을 어떻게 줄일 수 있을까

    이 RTO_MIN은 ip route를 통해서 바꿀 수 있다.

    세션 별로 바꾸는 것은 아니라, 네트워크 디바이스를 기준으로 바꿀 수 있다. 

     

    예시) 0.0.0.0 네트워크로 나갈 때 eth0 디바이스를 거치며, 이때 rto_min을 100ms으로 변경한다.    

    ip route change default via eth0 rot_min 100ms

     

     

    적절한 애플리케이션 타임 아웃 

    TCP 재전송이 일어나면 애플리케이션은 요청에 대한 응답을 받지 못했기 때문에 타임 아웃이 발생한다. 

    그리고 타임 아웃이 언제 발생시킬지가 중요한데, 이는 타임 아웃 임계치를 어떻게 설정했느냐에 따라 달라질 수 있다.

    애플리케이션에서 발생할 수 있는 타임 아웃은 다음과 같은 것들이 있다.  

     

    1. Connection timeout

    최초 TCP Handshake 과정에서 실패한 경우다. 

    이 케이스는 SYN나 SYN + ACK 패킷이 유실된 것이다. 

     

    애플리케이션에서는 Connection timeout을 3초 이상 설정하는 것을 권장한다. 

    왜냐하면 한번의 재전송 정도는 커버할 수 있도록 타임아웃을 설정해야 하기 때문이다.  

    한 번의 재전송은 충분히 일어날 수 있지만, 두 번 이상의 재전송은 문제가 있을 가능성이 크기 때문에

    두번째 재전송은 고려하지 않는 방식을 주로 택한다.   

    타임아웃은 첫번째 SYN 패킷에 대한 타임 아웃(init RTO 1초) + 상대방의 SYN+ACK 재전송(initRTO 1초)보다 큰 3초 이상을 설정하는 것을 권장한다.  

     

     

    2. Read Timeout

    해당 케이스는 이미 연결되어 있는 세션 중에서 데이터를 읽으려다가 타임아웃이 발생한 것이다. 

    주로 커넥션 풀 방식을 사용해서 특정 서버와 다수의 네트워크 세션을 만들어놓은 상태에서 발생하는 타임아웃이다. 

     

    애플리케이션에서는 일반적으로 300ms 이상 설정하는 것이 권장된다. 

    connection timeout과 마찬가지로 한 번의 재전송은 커버할 수 있어야 한다. 

    (두 번 이상의 재전송이 일어나면 이는 이미 문제가 있다는 것이므로 두 번째 재전송은 생각하지 않는다)

    RTO_MIN이 보통 200ms이고, 200ms보다 작게 되면 한번의 재전송에서 타임아웃이 나게 된다. 

    RTT 자체가 긴 경우는 RTO가 200ms이상일 수 있기 때문에 300ms이상 설정해야 된다. 

     

     

    Reference

    Devops와 SE를 위한 리눅스 커널 이야기 

    반응형

    '커널(Kernel)' 카테고리의 다른 글

    Application 튜닝해서 성능 최적화하기  (0) 2022.12.26
    Linux Dirty page가 I/O에 미치는 영향  (0) 2022.12.19
    Linux TCP Keepalive 정리  (0) 2022.12.11
    Linux TIME_WAIT 소켓 정리  (0) 2022.12.11
    Linux NUMA 아키텍처 정리  (0) 2022.12.05

    댓글

Designed by Tistory.