안동민 개발노트 아이콘

안동민 개발노트

6장 : TCP

흐름 제어와 혼잡 제어

TCP가 신뢰성을 보장한다는 것은, 단순히 패킷이 빠지면 다시 보낸다는 뜻만은 아닙니다. 송신 측은 수신 측의 처리 능력을 넘어서 보내면 안 되고, 동시에 네트워크 전체의 혼잡도 고려해야 합니다.

이 두 제어가 흐름 제어(Flow Control)혼잡 제어(Congestion Control)입니다. 둘 다 “얼마나 보낼 수 있는가”를 제한하지만, 보호 대상이 다릅니다.


흐름 제어: 수신 측 보호

흐름 제어는 송신 측이 수신 측의 버퍼와 처리 속도를 넘어서 데이터를 밀어 넣지 않게 조절하는 메커니즘입니다. 목적은 수신 버퍼 오버플로우를 줄이는 것입니다.

수신 측에는 도착한 데이터를 임시로 저장하는 수신 버퍼(Receive Buffer)가 있습니다. 애플리케이션이 이 버퍼에서 데이터를 읽어가는 속도보다 네트워크로 들어오는 속도가 빠르면 버퍼 여유가 줄어듭니다. TCP는 이 여유 공간을 윈도우 값으로 광고해 송신 측 전송량을 제한합니다.


슬라이딩 윈도우와 rwnd

TCP 흐름 제어는 슬라이딩 윈도우(Sliding Window) 방식으로 동작합니다. 수신 측은 자신이 더 받을 수 있는 양을 rwnd(Receive Window), 즉 advertised receive window로 알려줍니다. 이 값은 단순한 순간 버퍼 여유뿐 아니라 수신 버퍼 자동 튜닝과 silly window avoidance 같은 정책의 영향을 받을 수 있습니다.

송신 측은 아직 누적 ACK를 받지 못한 데이터량이 rwnd를 넘지 않도록 조절합니다. TCP 헤더의 Window 필드는 원래 16비트라 최대 65,535바이트를 표현하지만, 고속·장거리 네트워크에서는 Window Scale option을 사용해 더 큰 수신 윈도우를 광고할 수 있습니다.

수신 애플리케이션이 버퍼를 비우면 rwnd가 커지고, 처리가 밀리면 rwnd가 줄어듭니다. rwnd가 0이 되면 송신 측은 일반 데이터 전송을 멈추고, persist timer에 따라 Zero Window Probe를 보내 윈도우가 다시 열렸는지 확인합니다.


혼잡 제어: 네트워크 보호

혼잡 제어는 네트워크 경로의 혼잡 상태를 추정하고 송신량을 조절하는 메커니즘입니다. 흐름 제어가 수신자를 보호한다면, 혼잡 제어는 라우터 큐와 경로 전체를 보호합니다.

네트워크가 혼잡해지면 큐 지연이 커지고, 패킷 손실이나 ECN 표시가 발생할 수 있습니다. TCP 송신자는 cwnd(Congestion Window)를 관리해 한 번에 네트워크에 outstanding 상태로 남겨둘 수 있는 데이터량을 제한합니다.

실제 전송 가능량은 단순히 rwnd 하나로 정해지지 않습니다. TCP 송신자는 flight size, 즉 아직 ACK되지 않은 데이터량이 min(rwnd, cwnd) 범위를 넘지 않도록 보냅니다. 지금 즉시 추가로 보낼 수 있는 양은 이 상한에서 현재 flight size를 뺀 값이며, 송신 버퍼, 애플리케이션 데이터, MSS, pacing도 함께 제약합니다.


Slow Start

TCP 연결이 시작될 때 송신자는 경로 용량을 아직 모릅니다. 그래서 작은 혼잡 윈도우에서 시작해 ACK를 받을 때마다 cwnd를 늘리며 경로를 탐색합니다. 이것이 Slow Start입니다.

전통적인 설명에서는 cwnd가 1 MSS에서 시작한다고 배우지만, RFC 5681은 MSS에 따라 2~4 SMSS 범위의 initial window를 제시했고, RFC 6928은 IW10을 실험적 권고로 다룹니다. 많은 현대 구현은 환경과 커널 설정에 따라 더 큰 초기 윈도우를 사용합니다. 따라서 “1 MSS에서 시작한다”는 말은 알고리즘 직관을 설명하는 단순화로 보는 편이 안전합니다.

Slow Start 동안 cwnd는 새 데이터를 누적 확인하는 ACK를 받을 때 증가합니다. 모든 세그먼트가 제때 ACK되면 대략 매 RTT마다 두 배처럼 보일 수 있지만, delayed ACK, ACK 손실, pacing, 구현 방식에 따라 실제 증가는 달라집니다.

cwnd가 ssthresh(Slow Start Threshold)에 도달하거나 혼잡 신호가 감지되면 Slow Start를 멈추고 Congestion Avoidance로 전환합니다.


Congestion Avoidance와 손실 반응

Congestion Avoidance에서는 cwnd 증가가 느려집니다. 전통적인 Reno 계열은 ACK마다 SMSS*SMSS/cwnd에 가까운 방식으로 증가해, 전체적으로는 대략 매 RTT마다 1 SMSS 정도 증가하는 additive increase처럼 보입니다. 이 설명은 Reno식 AIMD 기준이며 CUBIC, BBR 같은 알고리즘은 다른 방식으로 동작합니다.

손실 신호에 대한 반응은 감지 방식에 따라 다릅니다. 재전송 타이머가 만료되면 심한 혼잡으로 보고 ssthresh를 flight size 기준으로 줄이고, cwnd를 loss window로 낮춘 뒤 다시 Slow Start로 회복합니다. 반면 중복 ACK 3개는 일부 세그먼트가 빠졌지만 뒤의 데이터가 도착하고 있다는 신호일 수 있으므로 Fast Retransmit과 Fast Recovery로 더 빠르게 복구합니다.


Fast Retransmit과 Fast Recovery

수신 측이 순서가 맞지 않는 세그먼트를 받으면 기대하는 다음 시퀀스 번호에 대한 중복 ACK를 보냅니다. 송신 측이 같은 ACK를 여러 번 받으면 특정 세그먼트가 손실됐다고 추정할 수 있습니다.

전통적인 Reno 설명에서는 중복 ACK 3개가 Fast Retransmit의 대표 트리거입니다. 이후 Fast Recovery에서 cwnd를 크게 낮추되 타임아웃처럼 1까지 떨어뜨리지는 않고 회복을 시도합니다. 실제 구현은 NewReno, SACK, RACK/TLP 같은 손실 복구 방식과 함께 동작하므로 세부 동작은 운영체제와 커널 버전에 따라 달라질 수 있습니다.


현대 혼잡 제어 알고리즘

Reno/NewReno 이후 다양한 혼잡 제어 알고리즘이 개발되었습니다. 핵심 차이는 혼잡을 손실로 보느냐, 지연으로 보느냐, 또는 대역폭과 RTT 모델로 추정하느냐입니다.

알고리즘방식특징주로 만나는 환경
Reno/NewRenoLoss-basedSlow Start, CA, Fast Recovery의 전통적 기준TCP 기본 알고리즘 학습 기준
CUBICLoss-based손실 직전 Wmax 주변을 3차 함수로 탐색, 고BDP 경로에 강함Linux 계열에서 흔히 사용
BBRModel-based병목 대역폭과 RTT를 추정해 pacing rate를 조절Google 계열 서비스, 선택적 사용
VegasDelay-basedRTT 증가로 큐 형성을 추정학습·연구 예시
DCTCPECN-basedECN 마킹 비율로 cwnd를 비례 조절해 큐를 낮게 유지ECN이 잘 구성된 데이터센터
congestion_simulation.py
def simulate_slow_start(ssthresh, initial_cwnd=10, max_rounds=10):
    """단순화한 Slow Start → Congestion Avoidance 시뮬레이션"""
    cwnd = initial_cwnd
    phase = "Slow Start"
    total_sent = 0

    print(f"{'RTT':>4} {'Phase':>22} {'cwnd':>6} {'전송량':>8} {'누적':>8}")
    print("-" * 55)

    for rtt in range(1, max_rounds + 1):
        total_sent += cwnd
        print(f"{rtt:>4} {phase:>22} {cwnd:>6} {cwnd:>8} {total_sent:>8}")

        if phase == "Slow Start":
            cwnd *= 2
            if cwnd >= ssthresh:
                cwnd = ssthresh
                phase = "Congestion Avoidance"
        else:
            cwnd += 1

    return total_sent

print("=== initial_cwnd=10, ssthresh=40 예시 ===")
simulate_slow_start(ssthresh=40, initial_cwnd=10)

이 코드는 개념 확인용입니다. 실제 TCP는 byte 단위 cwnd, delayed ACK, pacing, SACK, RTO, 애플리케이션 송신량, rwnd 제한, 커널 구현에 영향을 받습니다.


실무에서의 혼잡 제어 확인

congestion_check.sh
#!/bin/bash

echo "=== 현재 사용 중인 혼잡 제어 알고리즘 ==="
cat /proc/sys/net/ipv4/tcp_congestion_control
# 예: cubic 또는 bbr

echo ""
echo "=== 사용 가능한 알고리즘 ==="
cat /proc/sys/net/ipv4/tcp_available_congestion_control
# 예: reno cubic bbr

echo ""
echo "=== TCP 연결별 윈도우와 RTT 확인 ==="
ss -ti | head -20
# 예: cwnd:10 ssthresh:65535 rtt:3.5/0.5

echo ""
echo "=== 전송 버퍼/수신 버퍼 범위 확인 ==="
cat /proc/sys/net/ipv4/tcp_rmem
cat /proc/sys/net/ipv4/tcp_wmem
# 형식: min default max (바이트)
파라미터설명볼 때의 기준
tcp_congestion_control혼잡 제어 알고리즘서비스 경로에서 측정 후 선택
tcp_rmem수신 버퍼 min/default/max고BDP 경로에서는 충분한 max와 auto tuning 확인
tcp_wmem송신 버퍼 min/default/max애플리케이션 송신량과 커널 auto tuning 확인
tcp_window_scaling64KB 초과 윈도우 광고 허용고속·장거리 TCP에서는 활성화가 중요
tcp_timestampsRTT 측정과 PAWS 등에 사용환경별 정책과 보안 고려를 함께 확인

bbr이 모든 서비스의 정답은 아닙니다. 대역폭, RTT, 손실 특성, 큐잉, 공정성, 커널 버전, 클라우드 네트워크 정책에 따라 cubic이 더 안정적인 경우도 있습니다. 운영에서는 변경 전후를 같은 트래픽 조건에서 측정해야 합니다.

다음 절에서는 실무에서 자주 만나는 TCP 심화 주제와 실무 이슈를 살펴보겠습니다.