[네트워크/Network] TCP 혼잡제어에 대한 정리

kindof

·

2021. 10. 8. 23:29

💡 1. TCP 혼잡제어

TCP 프로토콜하면 가장 먼저 떠오르는 특징은 무엇인가요?

 

아마 많은 분들이 '신뢰적인 전송 서비스'를 TCP의 가장 큰 특징으로 꼽으실텐데요. 그런데 이 특징만큼 중요한 또 다른 특징이 있습니다. 바로 TCP 프로토콜의 혼잡제어 기능입니다.

 

IP 계층은 네트워크 혼잡에 대해 종단 시스템에게 어떤 정보도 제공하지 않고 데이터그램을 운반하는 역할밖에 하지 않기 때문에, TCP는 네트워크 지원 혼잡제어가 아닌 종단 간의 혼잡제어를 사용합니다.

 

즉, TCP는 네트워크 혼잡에 따라 연결에 트래픽을 보내는 전송률을 각 송신자가 제한하도록 하는데요. 만약 TCP 송신자가 자신과 목적지 간의 경로에서 혼잡이 없다고 여기면 송신자는 송신율을 높이고, 반면 혼잡한 상황을 마주하면 송신자는 송신율을 줄입니다.

 

이번 포스팅에서 공부할 내용은 바로 이 부분에서 생기는 아래 물음에 대한 답에 관한 내용입니다.

 

Q-1. TCP 송신자는 트래픽 전송률을 어떻게 제한할까?

Q-2. TCP 송신자는 자신과 목적지 사이 경로의 혼잡을 어떻게 감지할까?  

Q-3. TCP 송신자는 혼잡한 상황에서 어떤 알고리즘을 이용해 송신율을 변화시킬까?

 

 

🤔 1. TCP 송신자는 트래픽 전송률을 어떻게 제한하는가

TCP 연결의 양 끝 각 호스트들은 수신 버퍼, 송신 버퍼, 그리고 몇 가지의 변수(LastByteRead, rwnd 등)로 구성되며, 이 때 송신 측에서 동작하는 TCP 혼잡제어 메커니즘은 추가적인 변수인 혼잡 윈도우(Congestion Window)를 이용합니다.

 

여기서 rwnd는 수신 측 TCP Receive Window 크기를 말하며, 수신측이 Ack 없이 한번에 데이터를 받을수 있는 크기를 말합니다. 그리고 cwnd는 송신 측 TCP Congestion Window 크기를 말하며, 송신 측이 Ack없이 한 번에 데이터를 보내는 크기로, 혼잡 제어를 위해 계속 변동하는 값입니다.

 

cwnd로 표시되는 혼잡 윈도우는 TCP 송신자가 네트워크로 트래픽을 전송할 수 있는 비율을 제한하는데요. 왜냐하면 송신하는 쪽에서 확인응답이 안 된 데이터의 양은 cwnd와 rwnd의 최솟값을 초과하지 않기 때문입니다. 즉, LastByteSent - LastByteAcked <= min(cwnd, rwnd)라는 식이 성립하는 것이죠.

 

만약 수신 윈도우의 제약 조건을 무시할 수 있을 정도로 TCP 수신 버퍼가 매우 크다고 가정하면, 송신자의 확인응답이 안 된 데이터의 양은 오로지 cwnd에 의해 한정되게 됩니다. 즉, 송신자의 확인응답이 안 된 데이터 크기를 제한하면 이에따라 송신자의 송신율을 간접 제한하는 것이죠. 

 

이를 이해하기 위해 손실과 패킷 전송 지연이 무시되는 가상의 연결 상태를 고려해보겠습니다. 그러면 대략적으로 매 왕복시간(RTT)의 시작 시점에 송신자는 앞에서 언급한 제약 조건에 따라 cwnd 바이트만큼의 데이터를 전송할 수 있고, RTT가 끝나는 시점에 데이터에 대한 확인응답을 수신합니다. 따라서 송신자의 송신율은 대략 cwnd/RTT(바이트/초)가 되죠. 결과적으로 cwnd 값을 조절하는 것은 송신자가 링크에 데이터를 전송하는 비율을 조절하는 것을 의미합니다.

 

 

🧐 2. TCP 송신자는 자신과 목적지 사이의 경로에 혼잡을 어떻게 감지하는가

'혼잡 감지'를 위한 기준을 마련하기 위해 타임아웃 또는 수신자로부터 3개의 중복된 ACK들이 수신되면 TCP 송신자 측에 "손실 이벤트"가 발생한다고 정의하겠습니다.

 

과도한 혼잡이 발생하면, 경로에 있는 하나 이상의 라우터 버퍼들에서 오버플로가 발생하고, 그 결과 데이터그램이 버려집니다. 버려진 데이터그램은 송신 측에서 손실 이벤트를 발생시키게 되고, 송신자는 송신자와 수신자 사이의 경로상에 혼잡이 발생했음을 알게 되죠.

 

한편 혼잡이 발생하지 않는 경우, TCP는 혼잡 윈도우의 크기를 증가시키기 위해 ACK응답을 사용하게 되는데, ACK응답이 상대적으로 늦은 속도로 도착한다면 혼잡 윈도우를 상대적으로 낮은 속도로 증가시키고, ACK응답이 높은 속도로 도착한다면 혼잡 윈도우를 더 빨리 증가시킵니다. 따라서 TCP는 ACK응답을 혼잡 윈도우 크기의 증가를 유발하는 트리거(Trigger) 용도로도 사용하는 것이죠. 이에 TCP를 자체 클로킹(self-clocking)이라고도 합니다.

 

 

📒 3. TCP 혼잡제어 알고리즘

이제 글의 서두에서 언급한 세번째 물음인 "TCP 송신자는 혼잡한 상황에서 어떤 알고리즘을 이용해 송신율을 변화시킬까?"에 대해 이야기하려고 합니다.

 

TCP 혼잡제어 알고리즘은 크게 (1) 슬로우 스타트(Slow Start), (2) 혼잡 회피(Congestion Avoidance), (3) 빠른 회복(Fast Recovery)가 존재하는데요. 참고로 슬로우 스타트와 혼잡 회피는 TCP의 필수적인 요소이고, 빠른 회복은 TCP에서 필수적인 요소는 아닙니다. 자 그럼 차례대로 각각의 알고리즘에 대해 살펴보겠습니다.

 

😓 3-1) 슬로우 스타트(Slow Start)

TCP 연결이 시작될 때, cwnd의 값은 일반적으로 1 MSS로 초기화되고 그 결과 초기 전송률은 대략 1MSS/RTT가 됩니다. 예를 들어, 만약 MSS = 500Byte이고 RTT = 200 msec라면 초기 전송률은 약 20kbps 정도가 되는 것이죠.

 

이 때, TCP 송신자가 사용할 수 있는 밴드폭은 MSS/RTT보다 훨씬 크기 때문에 하나의 ACK응답을 받을 때마다 cwnd를 1 MSS씩 증가시킵니다. 그러면 슬로우 스타트에서 TCP 전송률은 1 MSS -> 2 MSS -> 4 MSS ... 와 같이 작은 값으로 시작하지만 지수적으로 증가하죠.

 

하지만 언제까지 cwnd 값을 증가시킬 수 있을까요? 다음과 같은 방법들로 슬로우 스타트는 cwnd의 지수적 증가를 진행합니다.

(1) 타임아웃에 의한 손실 이벤트가 발생할 경우 cwnd 값을 1로 하고 새로운 슬로우 스타트를 시작합니다. 그리고 두번째 상태 변수인 슬로우 스타트의 임계치(Slow start threshold, ssthresh)값을 혼잡 상황을 감지했을 때 cwnd / 2로 정합니다.

(2) ssthresh가 혼잡 상황을 마주하게 된 시점에서 cwnd 값의 절반이므로, 이 값에 도달했을 때 cwnd를 두 배 증가시키면 다시 혼잡 상황에 놓일 가능성이 큽니다. 따라서 cwnd값이 ssthresh와 같아지면 슬로우 스타트를 종료합니다. 그리고 TCP는 혼잡 회피 모드로 전환됩니다.

(3) 만약 3개의 중복 ACK들이 검출되면 TCP는 빠른 재전송을 수행하여 빠른 회복 상태로 들어갑니다.

 

📊 3-2) 혼잡 회피(Congestion Avoid, CA)

위의 (2)번에서 설명한 것처럼, 혼잡 회피 상태로 들어가는 시점에서 cwnd의 값은 대략 혼잡 상황을 마주한 시점의 cwnd 값의 절반입니다. 그러므로 매 RTT마다 cwnd값을 두 배로 하기보다는 TCP는 좀 더 보수적인 접근법을 채택해서 매 RTT마다 "하나의" MSS만큼 cwnd를 증가시키는데요. 예를 들어, MSS가 1,460Byte이고 cwnd가 14,600Byte라면 10개의 세그먼트가 한 RTT 내에 송신될 수 있습니다. 각 ACK는 1/10 MSS만큼씩 혼잡 윈도우를 증가시키고, 이에 따라 모든 10개의 세그먼트가 수신되엇을 때의 ACK들 후에 하나의 MSS만큼만 혼잡 윈도우 값을 증가시킵니다.

 

하지만 혼잡 회피 상태에서도 RTT당 1 MSS의 선형적 증가는 무한히 지속될 수 없겠죠? TCP 혼잡 회피 알고리즘 역시 타임아웃이 발생하면 cwnd 값은 1로 하고, ssthresh값은 손실 이벤트가 발생할 때의 cwnd 값의 절반으로 설정합니다.

 

 

음, 그런데 맨 처음에 혼잡을 감지했을 때 cwnd를 1로 떨어뜨리지 말고 절반으로 떨어진 ssthresh로 바로 이동해서 1씩 증가시키는 게 낫지 않을까요? 

 

 

 

📖 3-3) 빠른 회복

TCP Tahoe라고 불리는 초기 TCP 버전은 타임아웃으로 표시되거나 3개의 중복 ACK응답이 표시되는 손실이 발생하면 무조건 혼잡 윈도우를 1MSS로 줄이고 ssthresh를 해당 시점의 절반으로 조정한 뒤 슬로우 스타트 단계로 들어갔습니다.

 

하지만 새로운 TCP 버전인 TCP Reno는 "빠른 회복"을 채택하여 TCP Tahoe와는 조금 다르게 손실 이벤트에 대응하는데요.

 

빠른 회복이 무슨 뜻인지는 아래 그림을 통해 살펴보겠습니다.

TCP Tahoe, TCP Reno의 비교

이 그림에서 초기 ssthresh는 두 버전 모두에서 8 MSS입니다. 처음 8번의 전송 동안은 Tahoe, Reno 모두 동일한 행동을 취하는데요. 혼잡 윈도우는 슬로우 스타트 동안에 지수적으로 빠르게 증가하고, 4번째 송신에서 임계치에 도달하게 됩니다. 그리고 이때부터는 혼잡 회피 모드로 들어가서 8번째 송신까지 혼잡 윈도우를 선형적으로 증가시키죠.

 

자, 그런데 손실 이벤트가 발생했을 때를 살펴보겠습니다. 이 상황에서 혼잡 윈도우의 크기는 빨간색 동그라미로 표시한 것처럼 12MSS입니다. 그러면 임계치는 0.5 * 12 = 6 MSS가 되겠죠? TCP Reno에서는 혼잡 윈도우가 6MSS로 되고 선형적으로 증가합니다. 반면 TCP Tahoe에서 혼잡 윈도우는 1 MSS로 설정되고 ssthresh에 도달할 때까지 지수적으로 증가하고, 그 이후에 선형적으로 증가함을 반복하죠.

 

그러면 결국 검은색으로 표시된 TCP Reno와 파란색으로 표시된 TCP Tahoe의 간격만큼 TCP 송신자가 전달할 수 있는 세그먼트의 수가 차이가 나게 됩니다.

 

 

✅ 나가면서

이번 시간에는 TCP 프로토콜의 중요한 기능 중 하나인 혼잡 제어에 대해 살펴봤는데요. 서두에 던졌던 질문 세 개를 다시 한 번 살펴보겠습니다.

 

Q-1. TCP 송신자는 트래픽 전송률을 어떻게 제한할까?

Q-2. TCP 송신자는 자신과 목적지 사이 경로의 혼잡을 어떻게 감지할까?  

Q-3. TCP 송신자는 혼잡한 상황에서 어떤 알고리즘을 이용해 송신율을 변화시킬까?

 

위 내용을 읽어보시면 이 질문들에 대한 답을 알게 되셨으리라 생각합니다.

 

요약하자면 1) TCP 송신자는 cwnd값의 조정을 통해 트래픽 전송률을 제한하고, 2) 타임아웃이나 ACK응답의 누적 등으로 손실 이벤트가 발생하면 혼잡을 감지합니다. 그리고 마지막으로 3) 혼잡 상황에서는 슬로우 스타트를 통해 cwnd를 1로 떨어뜨리고 ssthresh / 2 지점까지 지수적으로 다시 증가시키다가 혼잡 회피 모드로 들어가 +1 씩 MSS를 증가시킵니다. 이 때 TCP Reno는 빠른 회복을 채택하여 cwnd를 1로 떨어뜨리지 않고 ssthresh / 2로 떨어뜨린 다음 곧바로 +1 씩 선형적 증가를 시키죠.

 

이것으로 TCP 혼잡제어에 대한 정리를 마치겠습니다. 감사합니다.