TCP 혼잡 제어(Congestion Control)란?

인터넷 속도가 들쭉날쭉한 이유 중 하나는 TCP가 회선 상황을 살피며 보내는 양을 스스로 조절하기 때문입니다. 원리와 알고리즘별 차이, 숫자로 따라가는 계산 예시까지 정리했습니다.

한눈에 보기

TCP 혼잡 제어는 송신자가 "지금 네트워크가 감당할 수 있는 양"을 추측하면서 한 번에 내보내는 데이터 양(혼잡 윈도우, cwnd)을 늘렸다 줄였다 하는 규칙입니다. 네트워크 내부의 상태를 직접 알려 주는 장치가 없기 때문에, 송신자는 ACK가 오는 모양과 손실 여부라는 간접 신호로 추측합니다. 이 글에서는 왜 이런 장치가 생겼는지, RFC 5681이 정한 기본 동작, Reno·CUBIC·BBR의 철학 차이, 그리고 cwnd가 실제로 어떻게 변하는지를 계산 예시로 따라갑니다.

어디서 왔나: 혼잡 붕괴와 1988년 논문

초기 TCP는 ACK가 오지 않으면 곧바로 재전송하는 방식이어서, 회선이 막히면 재전송이 다시 회선을 막는 악순환(혼잡 붕괴)이 일어날 수 있었습니다. 이 문제를 다룬 대표 문헌이 1988년 Van Jacobson과 Michael Karels의 논문 "Congestion avoidance and control"이며, 이후 슬로 스타트·혼잡 회피 같은 개념이 TCP의 표준 동작으로 자리 잡았습니다. 이름이 알려진 Tahoe와 Reno는 각각 4.3BSD 릴리스의 이름에서 온 것입니다. 세 번의 중복 ACK가 왔을 때 Tahoe는 윈도우를 1 MSS로 낮추고 슬로 스타트로 돌아가고, Reno는 윈도우를 절반으로 줄이고 빠른 회복에 들어간다는 차이가 있습니다(위키백과 정리 기준). 현재 표준 문서는 2009년 9월의 RFC 5681입니다.

원리: RFC 5681이 정한 기본 동작

아래 카드는 개념을 '필요성 → cwnd → 슬로 스타트 → 혼잡 회피 → Reno → CUBIC → BBR' 순서로 나눈 것입니다. 숫자로 정리하면 RFC 5681의 핵심 규칙은 다음과 같습니다.

🚦

혼잡 제어가 필요한 이유

여러 연결이 하나의 회선을 함께 쓰는 인터넷에서, 모두가 최대 속도로 데이터를 쏟아내면 라우터 버퍼가 넘쳐 오히려 전체 성능이 나빠집니다.

TCP는 데이터를 보내고 받는 두 컴퓨터뿐 아니라, 그 사이를 지나는 라우터와 회선까지 공유해서 씁니다. 만약 송신 측이 회선 상황을 고려하지 않고 무작정 빠르게 데이터를 보내면 중간 라우터의 버퍼가 넘쳐 패킷 손실이 급증하고, 이는 재전송을 유발해 오히려 전체 네트워크의 효율이 급격히 떨어지는 "혼잡 붕괴"로 이어질 수 있습니다. 혼잡 제어는 이런 상황을 막기 위해 송신 속도를 네트워크 상태에 맞게 스스로 조절하는 TCP의 핵심 메커니즘입니다.

📦

혼잡 윈도우(cwnd)란

상대방의 확인 응답을 기다리지 않고 한 번에 내보낼 수 있는 데이터 양을 나타내는 값으로, 혼잡 제어 알고리즘이 이 값을 계속 조절합니다.

TCP 송신 측은 "혼잡 윈도우(cwnd, congestion window)"라는 값만큼의 데이터를 확인 응답(ACK) 없이도 미리 내보낼 수 있습니다. 이 값이 클수록 한 번에 더 많은 데이터를 보낼 수 있어 처리량이 늘지만, 너무 크면 네트워크가 감당하지 못해 손실이 발생합니다. 혼잡 제어 알고리즘의 핵심은 결국 이 cwnd 값을 상황에 맞게 늘렸다 줄였다 하는 규칙을 정하는 것입니다.

🐢

슬로우 스타트 - 조금씩 늘려가며 속도 탐색

연결 초반에는 작은 혼잡 윈도우로 시작해, 응답이 성공적으로 올 때마다 지수적으로 윈도우를 키워가며 네트워크가 감당할 수 있는 속도를 빠르게 찾아갑니다.

연결이 막 시작되면 TCP는 네트워크 상태를 전혀 알지 못하므로, 매우 작은 혼잡 윈도우(보통 패킷 몇 개 수준)로 조심스럽게 시작합니다. 확인 응답을 받을 때마다 윈도우 크기를 두 배로 늘려가며 빠르게 적정 속도를 탐색하다가, 손실이 발생하거나 정해둔 임계값에 도달하면 슬로우 스타트를 마치고 좀 더 완만하게 늘리는 혼잡 회피 단계로 넘어갑니다.

📉

혼잡 회피 - 손실 신호를 받으면 속도를 줄이는 방식

패킷 손실이 감지되면 네트워크가 혼잡하다는 신호로 해석해 혼잡 윈도우를 큰 폭으로 줄인 뒤, 다시 조금씩 늘려가며 적정선을 찾습니다.

전통적인 혼잡 제어는 패킷 손실을 "네트워크가 혼잡하다"는 신호로 해석합니다. 손실이 감지되면 혼잡 윈도우를 절반으로 줄이는 등 보수적으로 대응한 뒤, 이후에는 손실이 없는 동안 윈도우를 선형적으로 서서히 늘려가는 방식으로 적정 전송 속도를 계속 찾아갑니다. 이 늘렸다 줄이는 패턴이 반복되면서 그래프로 그리면 톱니 모양의 특징적인 곡선이 나타납니다.

🕰️

Reno·New Reno - 손실 기반 고전 알고리즘

고전적인 초기 혼잡 제어 알고리즘으로, 패킷 손실만을 혼잡의 신호로 삼는 가장 단순하고 오래된 방식입니다.

TCP Reno는 슬로우 스타트와 혼잡 회피, 빠른 재전송·빠른 회복 기법을 결합해 손실이 발생하면 즉시 윈도우를 줄이고 재빠르게 회복하도록 설계된 알고리즘입니다. New Reno는 한 번에 여러 패킷이 손실됐을 때의 회복 속도를 개선한 버전입니다. 다만 대역폭이 매우 큰 고속 회선에서는 손실 이후 회복 속도가 느려 회선을 충분히 활용하지 못하는 한계가 있습니다.

📐

Cubic - 리눅스 기본값, 고대역폭 환경에 맞춘 개선

혼잡 윈도우가 시간에 따라 3차 함수(큐빅) 곡선을 그리며 늘어나도록 설계해, 손실 이후에도 대역폭을 더 빠르게 회복합니다.

Cubic은 오늘날 리눅스 커널의 기본 혼잡 제어 알고리즘으로, 윈도우 크기를 시간의 3차 함수로 조절합니다. 손실 직후에는 빠르게 이전 수준 가까이 회복한 뒤 점점 완만하게 증가시키다가, 이전에 손실이 발생했던 지점 근처에서는 다시 신중하게 접근하는 방식으로 동작합니다. 이 방식은 대역폭·지연시간이 큰 고속 장거리 회선에서 Reno 계열보다 훨씬 효율적으로 대역폭을 활용합니다.

📡

BBR - 손실 대신 대역폭·지연을 직접 측정하는 새로운 접근

구글이 개발한 BBR은 패킷 손실을 기다리는 대신, 실제 병목 대역폭과 최소 왕복 지연시간을 직접 측정해 전송 속도를 정합니다.

Reno나 Cubic 같은 손실 기반 알고리즘은 "손실이 나야만" 네트워크가 혼잡하다고 판단하는데, 이는 이미 버퍼가 가득 찬 뒤에야 반응한다는 한계가 있습니다(버퍼블로트 문제와 연결됩니다). BBR(Bottleneck Bandwidth and Round-trip propagation time)은 대신 실시간으로 병목 구간의 대역폭과 지연시간이 가장 짧을 때의 값을 계속 추정해, 손실이 발생하기 전에 최적의 전송 속도를 유지하려고 시도합니다. 유튜브 등 구글의 여러 서비스에 적용되어 있으며, 리눅스 커널에서도 선택적으로 사용할 수 있습니다.

세 가지 접근 비교

알고리즘혼잡 신호윈도우 증가 방식손실 뒤 감소
Reno 계열패킷 손실RTT당 약 1 세그먼트(선형)절반(FlightSize/2)
CUBIC패킷 손실시간의 3차 함수β=0.7배(RFC 9438 권장값)
BBR측정한 대역폭과 최소 지연모델 기반(손실에 직접 반응하지 않음)모델에 따라 조정

CUBIC은 RFC 9438(2023년 발행, RFC 8312를 대체)에서 윈도우를 W_cubic(t) = C·(t−K)³ + W_max로 두고 C=0.4, β_cubic=0.7을 권장합니다. K는 손실 뒤 윈도우가 W_max로 되돌아가는 데 걸리는 시간입니다. 리눅스에서는 2.6.19 이후 CUBIC이 기본이라고 정리됩니다. 윈도우는 Compound TCP가 기본 알고리즘으로 소개되지만, 버전과 설정에 따라 다를 수 있습니다(위키백과 기준).

직접 계산해 보기: cwnd는 이렇게 움직입니다 (가상 예시)

아래는 실제 측정이 아니라 규칙을 이해하기 위한 가상 예시이며, 단위는 세그먼트, 값은 node로 검산했습니다. 세그먼트 크기는 1460바이트라고 가정해 RFC 5681의 초기 윈도우 상한 3세그먼트로 시작하고, ssthresh는 16으로 둡니다.

RTT 번호0123456
cwnd(세그먼트)361216171819

2번째 RTT까지는 매번 두 배(슬로 스타트)이고, 12에서 다음에 24가 되면 ssthresh 16을 넘으므로 16에서 멈춘 뒤 RTT당 1씩 늘어나는 혼잡 회피로 들어간 것으로 단순화했습니다(실제로는 ACK 단위로 증가하므로 약간 다를 수 있습니다). 이제 cwnd=20일 때 중복 ACK 3개로 손실이 감지되었다고 하면, ssthresh = max(20/2, 2) = 10, 빠른 회복 진입 시 cwnd = 10 + 3 = 13이고, 회복이 끝나면 10에서 다시 선형으로 늘어납니다. 손실 한 번에 윈도우가 절반으로 줄어드는 것이 톱니 모양의 이유입니다.

CUBIC도 같은 방식으로 따라가 보겠습니다(역시 가상 예시). 손실 직전 W_max=100세그먼트였다면 손실 뒤 cwnd=100×0.7=70이고, K=∛(100×0.3/0.4)≈4.22초입니다. 그 뒤 윈도우는 t=1초에 약 86.7, 2초에 약 95.6, 3초에 약 99.3, 4초에 약 100.0으로 W_max 근처까지 빠르게 회복하고, 5초에 약 100.2, 6초에 약 102.3, 8초에 약 121.7로 W_max를 넘어서면서 다시 공격적으로 탐색합니다. 같은 상황에서 Reno 계열은 70에서 100까지 RTT당 1씩 늘리므로 30 RTT가 걸립니다. RTT가 200ms라면 약 6초로, CUBIC의 약 4.2초보다 오래 걸립니다. CUBIC의 증가가 RTT가 아닌 경과 시간에 맞춰 있다는 점이 고속·장거리 회선에서의 이점으로 설명됩니다(RTT가 매우 짧으면 Reno와 비슷하게 동작하도록 한 구간도 규격에 있습니다).

실제 사례 두 가지

사례 1. 유튜브에 적용된 BBR (2016년 이후)

BBR은 2016년 구글에서 개발된 모델 기반 알고리즘으로, 최근 송신 구간에서 관측한 최대 대역폭과 왕복 지연시간으로 네트워크 모델을 만들어 전송 속도를 정합니다. 위키백과 정리에 따르면 유튜브에 적용했을 때 BBRv1은 평균 처리량이 약 4%, 일부 국가에서는 최대 14% 높아졌다고 합니다. 또 QUIC에서도 BBR을 쓸 수 있습니다. 배울 점은 손실이 없어도 큐가 쌓이는 지연의 문제가 있고, 그에 대한 해법이 손실 기반 접근과 다른 방향으로도 가능하다는 것입니다. 다만 이 수치는 구글 환경에서의 결과이므로 모든 네트워크에 그대로 적용되지는 않습니다.

사례 2. 리눅스 기본값이 CUBIC이 된 것 (커널 2.6.19 이후)

리눅스 커널의 기본 혼잡 제어 알고리즘은 CUBIC이며, 위키백과는 리눅스 커널 2.6.19부터 기본값이라고 설명합니다. 그 때문에 웹 서버의 응답 속도 체감은 서버 쪽 알고리즘에 크게 영향을 받고, 서버 관리자는 커널 설정으로 BBR 등 다른 알고리즘을 선택해 볼 수 있습니다. 배울 점은 혼잡 제어가 송신 측 구현의 문제여서, 사용자는 바꾸기 어렵지만 서버 운영자의 선택이 체감 품질에 영향을 준다는 것입니다.

실전 점검

흔한 오해와 정정

한계와 더 알아볼 점

혼잡 제어는 네트워크 내부를 보지 못한 채 간접 신호로 추측하기 때문에 항상 오판 가능성이 있습니다. 손실 기반은 손실의 원인이 혼잡인지 무선 간섭인지 구분하기 어렵고, 측정 기반은 측정이 부정확하면 속도가 흔들릴 수 있습니다. 서로 다른 알고리즘이 한 회선을 나눠 쓸 때의 공정성도 알고리즘 설계의 지속적인 관심사입니다. 이 글의 성능 수치는 위키백과의 정리이며, 구체적 수치가 필요하면 원 논문과 RFC를 확인하세요.

TCP 자체의 기본 원리도 궁금하다면

혼잡 제어는 TCP의 신뢰성 보장 메커니즘 중 하나입니다. TCP와 UDP의 기본적인 차이가 궁금하다면TCP와 UDP 차이 완전정복도 확인해보세요.

버퍼블로트 문제와도 연결됩니다

손실 기반 알고리즘이 버퍼가 가득 찬 뒤에야 반응하는 문제는버퍼블로트(Bufferbloat)란? 게임·화상회의만 끊기는 이유에서 다루는 현상과 밀접하게 연결되어 있습니다.

자주 묻는 질문

내가 쓰는 혼잡 제어 알고리즘을 직접 바꿀 수 있나요?

리눅스에서는 커널 설정으로 Cubic, BBR 등 사용할 알고리즘을 선택할 수 있습니다. 다만 일반 사용자가 쓰는 윈도우·맥 운영체제나 대부분의 웹 브라우저는 이런 설정을 직접 노출하지 않으며, 보통 서버 쪽 설정이 실제 체감 속도에 더 큰 영향을 줍니다.

UDP를 쓰는 스트리밍이나 게임에도 혼잡 제어가 있나요?

UDP 자체에는 혼잡 제어 기능이 없지만, UDP 위에서 동작하는 QUIC 같은 최신 프로토콜은 자체적으로 혼잡 제어 로직을 구현해 두고 있습니다. 반면 혼잡 제어가 전혀 없는 UDP 애플리케이션은 네트워크가 혼잡해도 속도를 줄이지 않아, 다른 트래픽에 부담을 줄 수 있습니다.

cwnd와 수신 윈도우(rwnd)는 어떻게 다른가요?

rwnd는 수신자가 "내 버퍼에 이만큼까지 받을 수 있다"고 알려 주는 값이고, cwnd는 송신자가 네트워크 상태를 추측해 스스로 정하는 값입니다. 송신자는 둘 중 작은 쪽을 넘겨서 보내지 않습니다.

중복 ACK가 왜 손실 신호가 되나요?

수신자는 순서가 맞지 않는 세그먼트를 받으면 마지막으로 순서대로 받은 지점의 ACK를 다시 보냅니다. 같은 ACK가 3번 중복되면 중간 하나가 사라졌을 가능성이 높다고 보고, RFC 5681은 타이머를 기다리지 않고 재전송하도록 정합니다.

ping 시간이 길면 혼잡 제어 때문인가요?

꼭 그렇지 않습니다. 물리적 거리, 무선 구간, 라우터 큐 등 여러 원인이 있습니다. 다만 손실 기반 알고리즘이 라우터 버퍼를 가득 채우면 큐잉 지연이 커질 수 있어 버퍼블로트와 연결해서 살펴볼 만합니다.

이어서 읽어 보세요