congestion control: slow start, AIMD, fairness

Tasker_Jang·2026년 9월 28일
post-thumbnail

1. cwnd가 곧 속도 손잡이

송신 측은 congestion window(혼잡 윈도우, cwnd)라는 변수를 하나 둡니다. 확인받지 못한 채 네트워크에 떠 있어도 되는 바이트 수입니다. 전송 속도는 대략 다음과 같습니다.

rate ≈ cwnd / RTT  (bytes/sec)

cwnd만큼 보내고 한 바퀴 돌아오기를 기다리므로, RTT당 cwnd만큼 나가는 셈입니다. RTT는 마음대로 바꿀 수 없으니 조절할 수 있는 것은 cwnd 하나입니다. 실제 송신 가능량은 flow control까지 고려해 min(cwnd, rwnd)가 됩니다. 상대 버퍼와 네트워크 사정 중 더 빡빡한 쪽을 따르는 것입니다.

문제는 이렇게 바뀝니다. 손실이라는 신호만 보고 cwnd를 어떻게 올리고 내릴 것인가.

2. slow start: 빠르게 탐색하기

연결이 막 시작됐을 때는 적정 속도를 전혀 모릅니다. 그래서 1 MSS에서 출발합니다. 그리고 ACK를 하나 받을 때마다 cwnd를 1 MSS씩 늘립니다.

결과적으로 RTT마다 cwnd가 두 배가 됩니다. 1, 2, 4, 8로 늘어나는 지수 증가입니다. 이름이 slow start인 것은 증가가 느려서가 아니라 시작점이 1로 낮아서입니다. 아무것도 모르는 상태에서 조심스럽게 시작하되, 적정선을 빨리 찾기 위해 빠르게 올리는 전략입니다.

3. threshold와 AIMD

무한정 두 배씩 늘릴 수는 없습니다. 그래서 ssthresh(slow start threshold)라는 경계를 둡니다.

  • cwnd < ssthresh: slow start. RTT마다 두 배
  • cwnd ≥ ssthresh: congestion avoidance. RTT마다 1 MSS씩만 증가

여기서부터가 AIMD(Additive Increase Multiplicative Decrease)입니다.

  • additive increase: 잘 되고 있으면 RTT마다 1씩 조금씩 올려 여유를 탐색
  • multiplicative decrease: 손실이 감지되면 절반으로 뚝 떨어뜨림

올릴 때는 조심스럽게, 내릴 때는 과감하게가 핵심입니다. 혼잡은 빨리 해소해야 하지만 여유는 천천히 확인해도 되기 때문입니다. 이 때문에 cwnd 그래프는 톱니 모양이 됩니다.

4. Tahoe와 Reno: 손실에 어떻게 반응할 것인가

손실을 알아채는 경로는 두 가지입니다. timeout과 duplicate ACK 세 개. 두 버전은 이 둘을 같게 보느냐 다르게 보느냐에서 갈립니다.

TCP TahoeTCP Reno
역할최초의 혼잡 제어 내장 버전Tahoe에 fast recovery 추가
timeout 시ssthresh = cwnd/2, cwnd = 1, slow start부터 다시동일
중복 ACK 3개 시위와 동일하게 cwnd = 1ssthresh = cwnd/2, cwnd도 그 근처로만 낮추고 congestion avoidance로 복귀
판단두 신호를 같은 심각도로 봄중복 ACK는 가벼운 혼잡으로 봄
오버헤드회복이 느림상태 관리가 복잡

Reno의 근거는 단순합니다. 중복 ACK가 온다는 것은 뒤의 세그먼트들이 도착하고 있다는 뜻이므로 경로가 완전히 막힌 것은 아닙니다. 반면 timeout은 아무 응답도 없는 상태이니 훨씬 심각합니다. 그래서 Reno는 중복 ACK에 대해서는 cwnd를 1까지 떨어뜨리지 않고 절반 수준에서 이어갑니다. 이것이 fast recovery입니다.

ssthresh = 8에서 시작해 12에서 손실이 난 경우를 라운드별로 비교하면 차이가 분명합니다.

round12345678(손실)910
Tahoe1248910111212
Reno1248910111267

5. TCP는 왜 공평한가

같은 병목 링크를 두 연결이 나눠 쓴다고 해봅시다. 둘 다 AIMD를 따르면 결국 대역폭을 반씩 나눠 갖는 쪽으로 수렴합니다.

이유는 증가와 감소의 성질이 다르기 때문입니다. additive increase는 두 연결에 같은 양을 더합니다. 많이 쓰던 쪽이나 적게 쓰던 쪽이나 똑같이 1씩 올라가므로 둘의 격차는 그대로입니다. 반면 multiplicative decrease는 각자의 현재 값에 비례해 깎습니다. 많이 쓰던 쪽이 더 많이 깎이므로 격차가 줄어듭니다.

이 과정이 반복되면 격차는 계속 줄어들고 결국 균등한 지점 근처를 오가게 됩니다. 손실이라는 벌칙을 현재 사용량에 비례해 부과하는 것이 공평성의 원천입니다.

다만 조건이 붙습니다. RTT가 다르면 짧은 쪽이 더 자주 cwnd를 올리므로 유리합니다. UDP는 애초에 이 규칙을 따르지 않습니다. 그리고 한 애플리케이션이 TCP 연결을 여러 개 열면 그만큼 더 가져갑니다. 브라우저가 한 서버에 여러 연결을 맺는 것이 이와 무관하지 않습니다.

profile
ML Engineer 🧠 | AI 모델 개발과 최적화 경험을 기록하며 성장하는 개발자 🚀 The light that burns twice as bright burns half as long ✨

0개의 댓글