TCP 송신 속도는 rwnd가 정하지 않는다 — cwnd 상태 머신과 AIMD

seonwoo_jung·2026년 6월 23일

1. 도입

TCP handshake와 teardown 상태 머신은 한 번 정리해 둔 적이 있다. 그런데 정작 "연결이 열린 뒤 송신 속도는 무엇이 결정하는가"는 늘 흐릿했다. 막연히 "TCP는 수신 윈도우(rwnd)만큼 보낸다"고 알고 있었기 때문이다.

결론부터 말하면 그건 절반만 맞는 이야기다. 실제 송신 상한은 min(cwnd, rwnd)이고, 대역폭이 넉넉한 환경에서 흐름을 옥죄는 건 거의 항상 혼잡 윈도우(cwnd) 쪽이다. 이 글은 cwnd가 ACK 피드백과 손실 신호에 따라 어떤 상태들을 오가며 조절되는지를 RFC 5681과 6582를 따라가며 정리한다.

2. 핵심 개념: cwnd vs rwnd

RFC 5681이 정의하는 세 변수부터 분리하자(단위는 모두 바이트).

변수의미누가 정하나
cwnd네트워크에 띄울 수 있는 미확인 데이터 상한송신자 로컬 (패킷에 안 실림)
rwnd수신자 버퍼 여유수신자가 광고 (TCP Window 필드)
ssthreshSlow Start ↔ Congestion Avoidance 경계송신자

핵심은 흐름 제어(rwnd)와 혼잡 제어(cwnd)가 목적이 다른 별개의 메커니즘이라는 점이다. rwnd는 느린 수신자가 버퍼 오버플로로 죽지 않게 보호하고, cwnd는 네트워크 중간 라우터의 큐가 터지지 않게 보호한다. 실제 송신 가능량은 둘 중 작은 쪽, min(cwnd, rwnd)다.

"TCP 흐름은 rwnd가 정한다"는 통념이 어긋나는 지점이 여기다. LAN처럼 rwnd가 넉넉하면 병목은 거의 cwnd다.

3. 내부 동작: 네 상태를 오가는 cwnd

Slow Start — 이름과 달리 지수 증가

연결 직후나 타임아웃 복구 직후 진입한다. 초기 cwnd(IW)는 RFC 5681 기준 2~4 MSS였고, RFC 6928이 약 10 MSS로 확대했다.

규칙은 단순하다. ACK 하나가 올 때마다 cwnd += SMSS. 한 RTT 동안 cwnd만큼 세그먼트가 나가고 그만큼 ACK가 돌아오므로, RTT마다 cwnd가 대략 두 배가 된다. 이름은 "slow"지만 증가율로 치면 가장 공격적인 구간이다 — 단지 시작점이 낮을 뿐이다.

RTT 0:  cwnd=1  ┐ 각 ACK마다 +1
RTT 1:  cwnd=2  │ → RTT당 약 2배 (지수)
RTT 2:  cwnd=4  │
RTT 3:  cwnd=8  ┘
... cwnd >= ssthresh 도달 시 Congestion Avoidance로 전환

Congestion Avoidance — 선형 증가

cwnd >= ssthresh가 되면 진입한다. 더 키우다간 네트워크를 터뜨릴 수 있으니 RTT당 최대 1 SMSS만 늘린다. RFC 5681이 권장하는 근사식:

cwnd += SMSS * SMSS / cwnd     (ACK 수신마다)

cwnd가 SMSS개의 세그먼트로 차 있을 때 한 RTT에 그만큼 ACK가 오고, 각 ACK가 SMSS/cwnd씩 키우므로 합이 ≈1 SMSS가 된다. 이것이 AIMD의 Additive Increase다.

손실 감지의 두 경로 — 반응 강도가 다르다

cwnd를 줄이는 트리거는 "손실"인데, 감지 경로가 둘이고 심각도를 다르게 본다.

              ┌─ RTO 만료 (심각: ACK가 아예 안 옴)
손실 감지 ───┤      → ssthresh = max(in-flight/2, 2*SMSS)
              │        cwnd = 1 MSS,  Slow Start로 리셋
              │
              └─ 3 dup ACK (경미: 뒷 세그먼트는 도착 중)
                     → Fast Retransmit + Fast Recovery

왜 하필 3개인가? 네트워크 재정렬(reordering)로 ACK가 한두 개 뒤바뀌는 건 흔하다. dup ACK 1~2개를 손실로 단정하면 멀쩡한 데이터를 불필요하게 재전송하게 된다. RFC 5681은 임계값을 3으로 잡아 재정렬과 진짜 손실을 가른다.

Fast Recovery — cwnd를 1로 떨구지 않는 길

3 dup ACK를 받으면 RTO를 기다리지 않고 즉시 빠진 세그먼트를 재전송(Fast Retransmit)한 뒤 Fast Recovery로 들어간다.

  1. ssthresh = max(FlightSize / 2, 2*SMSS) — 절반으로 깎음(Multiplicative Decrease).
  2. cwnd = ssthresh + 3*SMSS — window inflation. 3을 더하는 건 이미 네트워크를 빠져나가 수신자에 도착한 세그먼트 3개를 회계상 반영하는 것이다.
  3. 추가 dup ACK마다 cwnd += SMSS로 계속 부풀리고, 여유가 생기면 새 데이터를 보낸다.
  4. 재전송을 ack하는 새 ACK가 오면 cwnd = ssthresh로 deflate하고 Congestion Avoidance로 복귀.

NewReno(RFC 6582)는 한 윈도우에서 여러 세그먼트가 동시에 손실된 경우를 보강한다. Recovery 진입 시점의 최고 시퀀스를 recover로 기록해 두고, 새 ACK가 이를 다 덮지 못하는 부분 ACK(partial ACK) 면 Recovery를 벗어나지 않고 다음 빠진 세그먼트를 곧장 재전송한다. 덕분에 다중 손실에도 RTO로 추락하지 않고 한 RTT에 하나씩 메운다.

4. 한 흐름 따라가 보기

RFC 5681 §3.1~§3.2 규칙을 SMSS=1000B, IW=1 MSS, ssthresh=64 MSS로 따라가 본다.

RTT0  cwnd=1   < ssthresh → SlowStart, ACK → cwnd=2
RTT1  cwnd=2   각 ACK +1 → cwnd=4
RTT2  cwnd=4            → cwnd=8
...   cwnd=64  >= ssthresh → Congestion Avoidance 진입
RTT k cwnd=64  ACK 64개, 각 +약 15.6B → RTT당 ≈+1 MSS → cwnd=65
...   여기서 3 dup ACK, FlightSize=80 MSS 가정
      ssthresh = max(80/2, 2) = 40 MSS
      cwnd = 40 + 3 = 43 MSS  (Fast Retransmit)
      재전송 ack하는 새 ACK → cwnd = 40 MSS, Congestion Avoidance 재개

cwnd가 80에서 40으로 반토막 났을 뿐 1로 추락하지 않은 점이 RTO 경로(cwnd=1)와의 결정적 차이다. AIMD — 천천히 올리고(+1/RTT) 손실엔 확 깎는(×1/2) — 가 만드는 톱니파(sawtooth) cwnd 그래프가 다수 흐름이 병목 대역폭을 공평하게 나눠 갖게 만드는 수렴 성질의 근거다.

리눅스라면 추상이 아니라 실제 값을 볼 수 있다.

# 활성 TCP 소켓의 cwnd, ssthresh, rtt를 직접 출력
ss -ti

5. 정리

TCP 송신자는 매 순간 min(cwnd, rwnd)만큼만 미확인 데이터를 띄우고, cwnd는 ACK라는 피드백 클럭과 손실이라는 혼잡 신호에 따라 Slow Start → Congestion Avoidance → Fast Recovery를 오가며 끊임없이 조절된다.

특히 기억할 세 가지: ① 병목은 rwnd가 아니라 보통 cwnd, ② Slow Start는 증가율로는 지수, ③ 손실이라고 항상 cwnd=1이 아니라 RTO일 때만 1로 리셋된다는 점이다.

더 파고들 거리:

  • CUBIC(RFC 8312) — cwnd를 RTT가 아닌 시간의 3차 함수로 키워 BDP가 큰 링크에서 Reno의 선형 한계를 넘는다. 리눅스 기본값으로 알려져 있다.
  • BBR — 손실이 아니라 병목 대역폭·RTT를 추정하는 모델 기반 제어로, AIMD 패러다임 자체를 벗어난다.
  • SACK(RFC 2018) — 어떤 세그먼트가 도착했는지 선택적으로 알려줘 NewReno의 "한 RTT에 하나씩 복구" 제약을 완화한다.

참고 자료

  • RFC 5681 — TCP Congestion Control
  • RFC 6582 — NewReno Modification to TCP's Fast Recovery
  • RFC 6928 — Increasing TCP's Initial Window (IW10)
  • RFC 8312 — CUBIC for Fast Long-Distance Networks
  • W. R. Stevens, TCP/IP Illustrated, Vol.1, 21장

0개의 댓글