네트워크 중간 경로가 막히지 않게 전송 속도를 조절하는 것. 흐름 제어가 상대를 배려한다면 이건 네트워크를 배려한다.
왜 필요한가
모두가 최대 속도로 보내면
- 라우터의 큐가 넘친다 → 패킷이 버려진다
- 재전송이 늘어난다 → 더 막힌다 → 붕괴(congestion collapse)
1986년에 실제로 일어났고, 그래서 혼잡 제어가 TCP 에 들어왔다
기본 뼈대 — AIMD
Additive Increase, Multiplicative Decrease
잘 가면 창을 조금씩 늘린다 (더하기) 손실이 나면 창을 절반으로 줄인다 (곱하기)
- 톱니 모양의 전송률
- 여러 흐름이 경쟁해도 결국 공평하게 수렴한다
네 단계
-
① 슬로우 스타트
- 창을 1부터 시작해 ACK 마다 2배로 (지수 증가)
- "슬로우" 는 시작이 느리다는 뜻이지 증가가 느린 게 아니다
-
② 혼잡 회피
- 임계치(ssthresh)를 넘으면 RTT 당 1씩만 (선형 증가)
-
③ 빠른 재전송
- 중복 ACK 를 3번 받으면 타임아웃을 기다리지 않고 즉시 재전송
-
④ 빠른 회복
- 창을 절반으로 줄이고 혼잡 회피로 복귀 (1로 떨어뜨리지 않는다)
손실을 혼잡 신호로 보는 것의 한계
무선 구간에서는 혼잡이 아니라 '전파 간섭' 으로도 패킷이 사라진다
- TCP 는 이걸 혼잡으로 오해하고 속도를 줄인다
- 무선에서 체감 속도가 안 나오는 원인 중 하나
버퍼블로트
- 라우터 버퍼가 크면 패킷이 안 버려지고 큐에 쌓이기만 한다
- 손실이 없으니 TCP 는 계속 속도를 올린다
- 지연만 폭증한다 ("인터넷이 느린데 속도 측정은 잘 나온다")
알고리즘 선택
sysctl net.ipv4.tcp_congestion_control # cubic (리눅스 기본)
sysctl net.ipv4.tcp_available_congestion_control
- CUBIC — 손실 기반. 고대역·장거리에 맞게 개선된 표준
- BBR — 대역폭과 RTT 를 '측정' 해 결정한다 (손실을 안 기다린다)
- 버퍼블로트 환경에서 특히 유리해 대형 서비스가 채택했다
면접 함정
- ❌ "슬로우 스타트는 천천히 늘린다" → 지수적으로 늘린다. 시작 값이 작을 뿐이다.
- ❌ "패킷 손실 = 회선 불량" → TCP에서는 정상적인 혼잡 신호다. 손실 0이 목표가 아니다.
톱니 모양을 눈으로
cwnd
│ ╱| ╱| ╱|
│ ╱ | ╱ | ╱ |
│ ╱ | ╱ | ╱ |
│╱ |╱ |╱ |
└──────────────────────────→ 시간
↑손실 ↑손실 ↑손실 (그때마다 절반으로)
여러 흐름이 이 패턴으로 경쟁하면 결국 대역폭을 고르게 나눠 갖는다
관측
ss -ti | grep -E 'cwnd|retrans'
sysctl net.ipv4.tcp_congestion_control
# BBR 로 바꾸기 (커널 4.9+)
sudo sysctl -w net.ipv4.tcp_congestion_control=bbr
ECN — 버리지 않고 알려 준다
기존: 라우터가 혼잡하면 패킷을 '버린다' → TCP 가 손실로 알아챈다 (재전송 비용)
- ECN: — 버리는 대신 헤더에 '혼잡 표시' 를 남긴다
- 수신자가 그 표시를 송신자에게 되돌린다
- 손실 없이 속도를 줄일 수 있다
양 끝과 중간 장비가 모두 지원해야 해서 보급이 더뎠다