1. 라이브러리
  2. TCP와 UDP
  3. TCP 심층 분석

업데이트됨 1개월 전

수천 개의 장치가 공유 인프라를 통해 데이터를 전송할 때, 인터넷은 어떻게 자체 트래픽으로 인한 혼잡을 피할까요? 바로 TCP 혼잡 제어 덕분입니다. 각 송신자가 네트워크가 명시적으로 보내지 않는 신호를 바탕으로 스스로 전송 속도를 조절하는 메커니즘입니다.

앞이 보이지 않는 상황에서의 추론

TCP 송신자는 네트워크 상태를 직접 알 수 없습니다. 어떤 라우터도 "지금 과부하 상태입니다"라고 알려주지 않고, 트래픽을 조율하는 중앙 기관도 없습니다. 송신자는 눈에 보이지 않는 경로를 통해 수신자와 연결되며, 자신이 전혀 모르는 수백만 개의 다른 연결과 인프라를 공유합니다.

그럼에도 TCP는 공유 인프라를 압도해서는 안 됩니다. 너무 빠르게 전송하면 라우터가 패킷을 드롭하고 지연이 급증하며 모두가 피해를 봅니다. 반대로 너무 느리게 전송하면 가용 대역폭이 낭비됩니다.

네트워크의 유일한 언어는 침묵입니다. 패킷은 도착해서 확인 응답을 받거나, 아니면 그냥 사라집니다. TCP는 이 부재를 읽는 법을 배웠습니다—응답 없음으로부터, 타이밍 패턴으로부터, 확인 응답 사이의 간격으로부터 혼잡을 추론합니다.

이는 수신자가 명시적으로 버퍼 공간을 알려주는 흐름 제어와는 근본적으로 다릅니다. 혼잡 제어는 그런 편의를 누릴 수 없습니다. 수십 년에 걸쳐 놀라울 정도로 효과적으로 다듬어진, 경험에 기반한 추론입니다.

왜 중요한가: 혼잡 붕괴

혼잡 제어가 없다면 네트워크는 스스로 무너집니다. 그 과정은 다음과 같습니다:

  1. 트래픽이 증가해 라우터가 패킷을 드롭하기 시작합니다
  2. 송신자가 손실을 감지하고 재전송합니다
  3. 재전송이 이미 과부하된 네트워크에 트래픽을 더합니다
  4. 더 많은 패킷이 드롭됩니다
  5. 더 많은 재전송이 밀려옵니다
  6. 무의미한 재전송으로 네트워크가 포화 상태가 되는 동안 실질적인 처리량은 0에 수렴합니다

이것은 고속 네트워크에서 펼쳐지는 공유지의 비극입니다. 자신의 패킷을 전달하려는 모든 송신자가 결국 모두의 상황을 악화시킵니다. 네트워크는 완전히 가득 찼으면서도 거의 아무것도 이루지 못하는 상태에 빠집니다.

TCP 혼잡 제어는 본능적으로 더 세게 밀어붙이고 싶은 바로 그 순간, 각 송신자가 정확히 물러서게 함으로써 이를 막습니다.

혼잡 윈도우: 스스로 부과하는 속도 제한

혼잡 제어의 핵심은 혼잡 윈도우(cwnd)입니다—전송 중인 미확인 데이터에 대한 송신자 측 제한입니다. 수신자에게도 흐름 제어에서 자체 제한(수신 윈도우, rwnd)이 있습니다. TCP는 둘 중 더 작은 값을 따릅니다:

유효 윈도우 = min(cwnd, rwnd)

cwnd가 얼마여야 하는지는 누구도 TCP에게 말해주지 않습니다. 송신자가 독립적으로 이 값을 유지하며, 네트워크 상태에 대한 추론을 바탕으로 조정합니다. 상황이 좋으면 cwnd가 커지고, 혼잡 징후가 보이면 줄어듭니다.

이 자기 조절이 인터넷을 대규모로 작동하게 합니다. 중앙 조율자도, 명시적인 신호도 없습니다. 오직 간접적인 증거를 바탕으로 각자의 속도 제한을 스스로 조정하는 수백만 개의 연결만 있을 뿐입니다.

느린 시작: 이름이 무색하게도

연결이 시작될 때 송신자는 가용 대역폭이나 경로 혼잡에 대해 아무것도 모릅니다. 네트워크는 알려주지 않습니다.

느린 시작은 TCP의 해법입니다: 신중하게 시작하되, 빠르게 가속합니다. 이름과 달리 이것은 지수적 성장입니다:

  • 작은 윈도우로 시작합니다 (일반적으로 1~10 세그먼트)
  • 확인 응답을 받을 때마다 cwnd를 세그먼트 하나씩 늘립니다
  • 매 RTT마다 cwnd만큼의 세그먼트를 전송하므로 윈도우는 매 RTT마다 두 배가 됩니다

진행 순서: 1 → 2 → 4 → 8 → 16 → 32...

다음 중 하나가 발생할 때까지 이 과정이 계속됩니다:

  1. 패킷 손실 감지—네트워크가 한계를 알립니다
  2. 느린 시작 임계값(ssthresh) 도달—이전 혼잡에서 기억된 한계
  3. 수신자 윈도우 도달—흐름 제어가 인계받습니다

느린 시작 임계값은 TCP가 기억하는 위험 신호입니다. 혼잡이 감지되면 TCP는 어느 지점에서 문제가 발생했는지 기록해둡니다. 다음번에는 그 지점에 도달하기 전에 더 완만한 성장 방식으로 전환합니다.

혼잡 회피: 조심스러운 선형 탐색

cwnd가 ssthresh에 도달하면, TCP는 자신이 위험 구역에 들어섰음을 압니다—이전에 혼잡이 발생했던 지점 근처입니다. 이제 전략은 지수적 성장에서 선형 성장으로 바뀝니다: 확인 응답이 얼마나 많이 오든 관계없이 RTT당 딱 하나의 세그먼트씩만 늘립니다.

이것이 TCP의 특징적인 톱니 패턴을 만들어냅니다:

  1. 윈도우가 선형으로 증가하며 더 많은 대역폭을 탐색합니다
  2. 혼잡 감지 (패킷 손실)
  3. 윈도우가 급격히 감소합니다
  4. ssthresh까지 느린 시작
  5. 선형 성장 재개
  6. 반복

이 톱니 패턴은 실패가 아닙니다—지금 이 순간 "너무 많음"이 어디인지를 TCP가 끊임없이 찾아가는 과정입니다. 네트워크는 변하고, 연결은 시작되고 끝납니다. 최적 전송 속도는 항상 움직이며, TCP는 주기적으로 한계를 초과하고, 초과를 감지하고, 물러서는 방식으로 이를 추적합니다.

침묵 속에서 혼잡 감지하기

네트워크가 혼잡을 직접 알려주지 않으므로, TCP는 증상을 관찰합니다:

패킷 손실

전통적인 신호. 두 가지 방식으로 나타납니다:

3회 중복 ACK: 수신자가 패킷 1, 2, 4, 5, 6을 받고 계속해서 패킷 2에 대한 ACK를 보내며 패킷 3을 요청합니다. 동일한 ACK 세 번은 다른 패킷들은 통과한 반면 하나의 세그먼트가 손실되었음을 시사합니다—경미한 혼잡입니다. 대응: cwnd를 대략 절반으로 줄이고 전송을 계속합니다.

타임아웃: 아무것도 돌아오지 않습니다. 많은 패킷이 손실되었거나 ACK 자체도 손실된 상황입니다. 심각한 혼잡입니다. 대응: cwnd를 최솟값으로 초기화하고 느린 시작을 처음부터 다시 시작합니다.

명시적 혼잡 알림 (ECN)

현대 네트워크는 침묵보다 더 나은 방법을 쓸 수 있습니다. ECN을 사용하면 라우터가 아무것도 드롭하기 전에, 큐가 쌓이고 있을 때 패킷에 표시를 남깁니다. 수신자는 이 표시를 다시 송신자에게 전달합니다.

네트워크가 마침내 말하는 법을 배우는 것입니다. "침묵은 문제를 뜻한다" 대신, ECN은 패킷 손실이라는 빨간 불이 켜지기 전에 노란 불 경고를 보냅니다. TCP는 미리 전송 속도를 줄일 수 있어, 더 낮은 지연 시간으로 더 높은 처리량을 달성합니다.

알고리즘의 진화

기본 프레임워크—윈도우 기반, 손실 기반 트리거—는 안정적으로 유지되어 왔습니다. 하지만 알고리즘마다 그 안에서 서로 다른 전략을 구현합니다:

TCP Reno (클래식)

가산 증가/승산 감소 (AIMD). RTT당 하나의 세그먼트씩 늘립니다. 손실 발생 시 윈도우를 절반으로 줄입니다. 단순하고 안정적이며 공평합니다. 그러나 대역폭이 넓고 지연이 긴 경로에서는 속도를 충분히 활용하기까지 오래 걸립니다—위성 링크에서 속도를 올리는 데 끝없이 시간이 걸립니다.

TCP Cubic (리눅스 기본값)

Cubic은 선형 증가 대신 3차 함수를 사용합니다. 혼잡 후에는 처음에 천천히 성장하다가, 추가 문제 없이 시간이 흐를수록 점점 공격적으로 성장합니다. 곡선의 중심은 마지막으로 혼잡이 발생한 윈도우 크기에 맞춰집니다—Cubic은 이전에 잘 동작했던 수준으로 빠르게 복귀한 다음, 그 너머를 조심스럽게 탐색합니다.

BBR (패러다임의 전환)

구글의 BBR은 손실을 주요 신호로 삼는 방식을 버렸습니다. 대신 직접 측정합니다:

  • 병목 대역폭: 데이터가 실제로 얼마나 빠르게 흐를 수 있나?
  • 최소 RTT: 큐 누적 없이 기본 지연 시간은 얼마인가?

BBR은 더 많은 대역폭을 탐색하는 단계와 쌓인 큐를 비우는 단계를 번갈아 가며 실행합니다. 핵심 통찰은 이렇습니다: 패킷 손실이 발생했다면 이미 네트워크를 혼잡하게 만든 것입니다. 왜 피해가 생길 때까지 기다립니까? BBR은 파이프를 가득 채우되 넘치지 않게 하려 합니다.

무선 네트워크처럼 임의적인 패킷 손실이 있는 경로에서, BBR은 잡음을 혼잡으로 오해하는 손실 기반 알고리즘을 크게 능가합니다.

핵심 구분

흐름 제어가 답하는 질문: 수신자가 이 데이터를 처리할 수 있나? 수신자가 명시적으로 버퍼 공간을 광고합니다. 송신자와 수신자 사이의 종단 간 제어입니다.

혼잡 제어가 답하는 질문: 네트워크가 이 데이터를 처리할 수 있나? 송신자가 간접적인 신호에서 용량을 추론합니다. 전체 경로와 공유되는 모든 인프라를 고려합니다.

두 제한은 동시에 적용됩니다. 네트워크 경로가 여유롭더라도 수신자의 윈도우를 초과해서는 안 됩니다. 수신 버퍼가 아무리 커도, 네트워크가 혼잡하면 데이터는 느리게 전달됩니다. TCP는 둘 중 더 엄격한 제약을 따릅니다.

TCP 혼잡 제어에 대해 자주 묻는 질문

혼잡할 때 네트워크가 TCP에게 그냥 알려주면 안 되나요?

인터넷은 "단순한 네트워크, 똑똑한 엔드포인트"라는 원칙 위에 세워졌습니다. 라우터는 단순하고 빠르게 설계되었습니다—그냥 패킷을 전달하는 것이 전부입니다. ECN은 이 철학에 변화를 가져왔지만, 라우터, 운영 체제, 애플리케이션 전반에 걸친 업그레이드가 필요해 도입이 더뎠습니다. TCP 혼잡 제어의 진정한 강점은 어떠한 네트워크 협조 없이도 동작한다는 점입니다.

TCP 혼잡 제어가 내 다운로드 속도에 어떤 영향을 미치나요?

웹 브라우징, 파일 다운로드, 스트리밍 등 모든 TCP 연결은 혼잡 제어를 사용합니다. 다운로드를 시작하면 느린 시작이 처음 몇 번의 왕복 동안 속도를 높여갑니다. 네트워크가 혼잡하면 속도가 떨어지고 톱니 패턴으로 회복됩니다. Cubic이나 BBR 같은 현대 알고리즘은 이전 알고리즘보다 더 높은 처리량을 달성합니다. 운영 체제 업그레이드가 네트워크 성능을 눈에 띄게 향상시키는 것도 이 때문입니다.

여러 TCP 연결이 혼잡한 링크를 공유하면 어떻게 되나요?

AIMD 동작에는 놀라운 특성이 있습니다: 공평성으로 수렴합니다. 너무 빠르게 전송하는 연결이 먼저 혼잡에 부딪혀 더 크게 물러납니다. 느리게 전송하는 연결은 성장할 여지가 있습니다. 시간이 지나면서 경쟁하는 연결들은 어떠한 중앙 조율 없이도 가용 대역폭을 균등하게 나눠 갖는 방향으로 수렴합니다.

네트워크가 한가한데도 연결이 느리게 느껴지는 이유가 무엇인가요?

느린 시작 때문입니다. 새로운 TCP 연결은 항상 신중하게 시작하며, 전속력에 도달하는 데 여러 번의 왕복이 필요합니다. 지연이 긴 연결(모바일 네트워크, 위성 링크)에서는 이 속도 증가 구간이 체감될 만큼 깁니다. HTTP/2와 HTTP/3는 더 적은 수의 연결에 여러 요청을 다중화해서 이 문제를 완화합니다. 느린 시작 패널티를 지불하는 횟수 자체를 줄이는 것입니다.

이 페이지가 도움이 되었나요?

😔
🤨
😃
TCP 혼잡 제어란 무엇인가 • 라이브러리 • Connected