콘텐츠로 이동

TCP 혼잡 제어는 왜 흐름 제어와 따로 봐야 할까요?

느려졌다고 해서 항상 패킷이 사라진 건 아니에요. TCP는 가끔 스스로 "지금은 길이 붐비는 것 같으니 조금만 보낼게요" 하고 물러서요.

TCP 재전송과 신뢰성에서는 TCP가 빠진 조각을 다시 보내는 법을 먼저 봤고, TCP 윈도우와 흐름 제어는 왜 같이 읽어야 할까요?에서는 받는 쪽이 지금 얼마나 더 받아둘 수 있는지를 광고하는 Window도 봤어요.

근데 여기서 이런 질문이 생기죠.

"좋아요, 받는 쪽 여유는 알겠어요. 근데 받는 쪽은 멀쩡한데도 왜 TCP가 갑자기 더 조심스럽게 보내는 것처럼 보일 때가 있죠?"

바로 그 장면을 여는 글이에요. 오늘은 수신자 버퍼 여유를 보는 흐름 제어와, 네트워크 길 자체가 붐비는지 보며 송신 속도를 조절하는 혼잡 제어를 나눠서 보고, cwnd, slow start, congestion avoidance, duplicate ACK 같은 말을 초심자 눈높이에서 같이 정리해볼게요.

이 글의 범위

여기서는 혼잡 제어의 큰 원리에 집중해요. 수신자 버퍼 여유를 광고하는 Windowrwnd 쪽은 TCP 윈도우와 흐름 제어는 왜 같이 읽어야 할까요?에서 이미 봤어요. 이번 글은 RFC 5681 기준의 Reno 계열 혼잡 제어 감각을 뼈대로 설명할 거예요. 다만 실제 운영체제는 CUBIC 같은 다른 알고리즘을 쓸 수도 있으니, 여기서는 모든 구현의 세부 차이까지 다 외우기보다 송신자가 왜 스스로 속도를 줄이게 되는지에 집중하면 충분해요.


일단 비유로 시작해볼게요

이번에는 차를 보내는 물류센터 출구 도로를 떠올려볼까요?

  • 창고 안에 실을 상자는 아주 많아요.
  • 받는 쪽 창고도 아직 받아둘 여유가 있을 수 있어요.
  • 근데 출구 도로가 이미 꽉 막혀 있으면, 차를 계속 밀어 넣는다고 더 빨리 도착하진 않겠죠.
  • 오히려 진입로까지 엉키고, 중간에 사고도 더 잘 날 거예요.

그래서 물류센터는 이렇게 생각해요.

  • 받는 창고 여유는 충분한가?
  • 도로 자체가 지금 붐비는가?

TCP도 비슷해요. 흐름 제어"상대 창고가 안 넘치게", 혼잡 제어"길 자체가 안 막히게" 조절한다고 보면 감이 와요.

기본편에서 잡은 감각 비유에서는 실제로는
흐름 제어 받는 창고 선반이 꽉 차지 않게 조절 rwnd / advertised window
혼잡 제어 도로가 막히면 출차 속도를 줄임 cwnd / congestion window
재전송 중간에 빠진 상자를 다시 보냄 timeout, fast retransmit
duplicate ACK "그 앞자리 상자가 아직 안 왔어요"라는 반복 연락 손실 가능성을 알려주는 신호
slow start 처음엔 길 상태를 몰라서 조심스럽게 탐색 작은 cwnd에서 시작해 빠르게 증가

즉, 흐름 제어는 받는 쪽 사정, 혼잡 제어는 길 사정이에요.


흐름 제어와 혼잡 제어는 뭐가 다를까요?

이 둘은 초심자 때 정말 자주 섞여요. 근데 여기서 한 번 선명하게 나눠두면 뒤 장면이 훨씬 덜 헷갈려요.

질문 흐름 제어 혼잡 제어
무엇을 보호하나 수신자 버퍼 네트워크 경로 전체
누가 기준을 말하나 받는 쪽이 Window 로 광고 보내는 쪽이 cwnd 를 내부적으로 계산
왜 줄이나 상대가 더 못 받을 것 같아서 길이 막히거나 손실/혼잡 신호가 보여서
대표 단서 rwnd, win 0, shrinking window timeout, duplicate ACK, ECE / CWR
질문으로 바꾸면 "상대가 더 받아둘 수 있나?" "길이 지금 감당할 수 있나?"

여기서 중요한 한 줄만 뽑으면 이거예요.

흐름 제어는 받는 쪽이 괜찮냐를 보고, 혼잡 제어는 길이 괜찮냐를 봐요.


실제 송신 한도는 어떻게 정해질까요?

그럼 실제로는 뭐가 최종 속도를 정할까요?

flowchart LR
    A[rwnd<br/><small>수신자가 광고한 여유</small>] --> C["min(rwnd, cwnd)"<br/><small>실제로 밀어 넣을 수 있는 양</small>]
    B[cwnd<br/><small>송신자가 계산한 혼잡 한도</small>] --> C
    C --> D[실제 전송 가능량]

이 그림이 핵심이에요. 실제로 보내는 쪽은 보통 rwndcwnd 중 더 작은 쪽에 맞춰 움직여요.

  • rwnd 가 더 작으면 → 상대가 더 못 받는 상황
  • cwnd 가 더 작으면 → 길이 더 못 버틸 것 같은 상황

그래서 "받는 쪽 여유는 충분한데도 왜 못 보내지?" 싶을 때는, rwnd 문제가 아니라 cwnd 쪽이 더 작은 상황일 수 있어요.

표지판 하나만 세워둘게요.

cwnd 는 보통 TCP 헤더 안에 그대로 실려서 보이는 값이 아니에요. rwnd 는 캡처 줄의 win 같은 힌트로 더 직접 보일 수 있지만, cwnd 는 송신자 내부 계산에 더 가까워요.


TCP 헤더에서는 어떤 신호가 보일까요?

혼잡 제어가 전부 TCP 헤더 안에 한 칸으로 들어 있는 건 아니에요. 이게 초심자에게 가장 헷갈리는 부분이죠.

Flags Window
CWR
ECE
기타 Flags
Window (rwnd)
수신자 광고값

이 그림에서 읽어야 할 건 이거예요.

  1. Window수신자 쪽 제한을 더 직접 보여줘요.
  2. ECE, CWRECN 혼잡 신호와 관련된 비트예요.
  3. 하지만 cwnd 자체는 보통 송신자 내부 상태라서, 캡처 줄에 그대로 cwnd=... 식으로 찍히는 게 아니에요.
신호/값 어디서 주로 보나 무슨 뜻인가 주의할 점
Window / win TCP 헤더, 캡처 줄 수신자가 더 받아둘 수 있는 양 혼잡 제어 값이 아님
ECE TCP Flags ECN 혼잡 신호 반사 항상 "이미 망했다"는 뜻은 아님
CWR TCP Flags 혼잡 윈도우를 줄였다는 응답 ECN 맥락에서 읽어야 함
duplicate ACK 캡처/분석 도구 빠진 조각 가능성 알림 재정렬 때문에 생길 수도 있음
cwnd 송신자 내부 상태 현재 길 사정을 반영한 송신 한도 캡처에 직접 안 보일 수 있음

즉, 혼잡 제어는 TCP 헤더 안의 한 칸으로 끝나는 주제가 아니라, 헤더 신호 + ACK 패턴 + 송신자 내부 계산이 같이 움직이는 주제예요.


보내는 쪽은 왜 처음엔 천천히 시작할까요?

처음 연결이 열렸다고 해서, 송신자가 바로 "좋아, 한꺼번에 엄청 보내자" 하면 위험하겠죠. 아직 길 상태를 모르니까요.

그래서 혼잡 제어는 보통 작은 cwnd 에서 시작해요.

flowchart LR
    A[처음 연결 시작<br/><small>작은 cwnd</small>] --> B[ACK가 잘 옴<br/><small>slow start</small>]
    B --> C[cwnd 빠르게 증가]
    C --> D[어느 지점 이후<br/><small>congestion avoidance</small>]
    D --> E[cwnd 더 천천히 증가]

이 흐름에서 핵심은 두 단계예요.

  • slow start — 처음엔 아직 길을 잘 모르니, ACK가 올 때마다 꽤 빠르게 늘려봐요.
  • congestion avoidance — 어느 정도 올라간 뒤에는, 계속 같은 속도로 키우지 않고 더 조심스럽게 늘려요.

RFC 5681 기준으로 이 두 단계는 아주 기본적인 혼잡 제어 뼈대예요. 초심자 입장에서는 수식보다 "처음엔 탐색, 그다음엔 조심" 이라는 감각이 먼저 중요해요.


길이 붐빈다고 판단하면 어떻게 줄일까요?

그럼 TCP는 언제 "아, 지금은 좀 줄여야겠다" 고 느낄까요?

대표적으로는 이런 신호들이 있어요.

  1. timeout — 너무 오래 ACK가 안 와요.
  2. duplicate ACK가 여러 번 반복됨 — 중간 조각이 비었을 가능성이 보여요.
  3. ECN 관련 신호 — 꼭 손실까지 나기 전에 길이 붐빈다는 힌트가 와요.
flowchart TD
    A[데이터 전송 중] --> B{혼잡 신호가 보이나?}
    B -->|아니오| C[cwnd 천천히 증가]
    B -->|duplicate ACK / timeout / ECN| D[cwnd 줄임]
    D --> E[다시 조심스럽게 증가]
    E --> B

여기서 표지판 하나만 세워둘게요.

duplicate ACK는 손실 가능성을 알려주는 강한 힌트예요. 하지만 패킷 재정렬 때문에도 비슷하게 보일 수 있으니, 무조건 손실 확정이라고 읽지는 않아요.


실제 캡처에서는 무엇을 읽어야 할까요?

혼잡 제어는 cwnd 가 바로 보이지 않는다고 했죠. 그래서 캡처에서는 직접 숫자 하나보다 증상 묶음을 더 봐야 해요. 즉 여기서 우리가 보는 건 cwnd 자체가 아니라, 송신자가 그렇게 판단했을 것 같은 패턴이에요.

14:32:01.123456 Out IP ... Flags [P.], seq 1001:2449, ack 5001, win 502, length 1448
14:32:01.158204 In  IP ... Flags [.], ack 1001, win 64240, length 0
14:32:01.165901 In  IP ... Flags [.], ack 1001, win 64240, length 0
14:32:01.172488 In  IP ... Flags [.], ack 1001, win 64240, length 0
14:32:01.179010 Out IP ... Flags [P.], seq 1001:2449, ack 5001, win 502, length 1448

이 장면에서 먼저 읽어야 할 신호 네 가지는 이거예요.

  1. 같은 ACK가 반복되는지
  2. 같은 seq 범위의 재전송이 뒤따르는지
  3. 수신자 win 은 넉넉한데도 송신 패턴이 움츠러드는지
  4. ECE / CWR 같은 ECN 비트가 보이는지

즉, 혼잡 제어는 이런 식으로 읽어요.

  • win 0 이다 → 수신자 여유 문제 쪽
  • duplicate ACK + 같은 seq 범위 재전송이 보인다 → 손실 / 혼잡 신호 쪽
  • win 은 멀쩡한데 송신 패턴이 작아진다 → cwnd 쪽 제한을 의심

이런 식으로 수신자 문제길 문제를 분리해서 보는 감각이 중요해요.


근데 왜? 재전송 글 다음에 이걸 알아야 할까요?

재전송만 알고 있으면 "느려졌다 = 잃어버렸다" 쪽으로 자꾸 생각이 기울어요. 근데 실제로는 유실 복구혼잡 회피가 같이 돌아가요.

1. 느림의 원인을 더 정확히 나눌 수 있어요

받는 쪽 여유 부족인지, 길 붐빔인지, 진짜 손실인지가 전부 같은 말은 아니거든요.

2. duplicate ACK의 의미가 더 또렷해져요

TCP 재전송과 신뢰성에서 duplicate ACK는 빠진 조각의 힌트였죠. 오늘 한 걸음 더 들어가면, 그건 동시에 혼잡 제어가 몸을 사리기 시작하는 계기가 되기도 해요.

3. 흐름 제어 글과 같이 읽어야 전체 그림이 닫혀요

TCP 윈도우와 흐름 제어받는 쪽 한도, 오늘 글이 길 한도를 맡아요. 이 둘을 같이 봐야 "지금 왜 이만큼밖에 못 보내지?" 라는 질문에 훨씬 정확히 답할 수 있어요.


잘못 읽기 쉬운 함정 다섯 가지

하나, Window 가 작으면 곧바로 혼잡 제어 문제라고 생각하기.
Window 는 먼저 수신자 여유 쪽 신호예요.

둘, 느려지면 무조건 패킷 손실이라고 단정하기.
손실 없이도 혼잡 제어 때문에 송신자가 더 조심스러워질 수 있어요.

셋, duplicate ACK가 보이면 손실이 100% 확정이라고 읽기.
강한 힌트는 맞지만, 재정렬 같은 다른 원인도 가능해요.

넷, cwnd 도 캡처 줄에 win 처럼 바로 보인다고 생각하기.
보통 cwnd 는 송신자 내부 상태에 더 가까워요.

다섯, RFC 5681 설명이 모든 운영체제의 유일한 모습이라고 믿기.
실제 구현은 CUBIC 같은 다른 알고리즘을 쓸 수 있어요. 오늘 글은 가장 기본적인 혼잡 제어 감각을 잡는 데 초점을 맞춘 거예요.


자, 정리해볼까요?

오늘 우리가 본 것

  • 흐름 제어는 받는 쪽 버퍼 여유를 보고, 혼잡 제어는 네트워크 길 사정을 봐요.
  • 실제 전송량은 보통 rwndcwnd 중 더 작은 쪽의 영향을 받아요.
  • cwnd 는 송신자 내부에서 계산되는 값이라, Window 처럼 캡처 줄에 그대로 보이지 않을 수 있어요.
  • 혼잡 제어는 보통 slow start → congestion avoidance → 혼잡 신호 시 감소 흐름으로 읽을 수 있어요.
  • duplicate ACK, timeout, ECN 관련 비트는 모두 혼잡을 읽는 실마리가 될 수 있어요.

결국 혼잡 제어를 이해한다는 건, TCP가 단순히 "빠진 걸 다시 보내는 친구" 를 넘어서, 길이 붐비면 스스로 발을 떼는 친구라는 걸 보는 일이에요.


이어서 보면 좋은 글