혼잡 제어 — TCP 는 네트워크의 혼잡에 양보한다
경로의 혼잡 정도는 아무도 알려 주지 않는다. TCP 는 cwnd 라는 자체 창을 배증으로 넓히고, 패킷 로스를 신호로 양보한다 — 탐색하면서 보내는 구조를 손계산으로 따라간다.
상대는 멀쩡해도, 길이 막혀 있다
제 4 장의 흐름 제어로, 상대를 익사시키는 일은 없어졌습니다. 그렇다면 rwnd 가 허락하는 한 전력으로 보내도 될까요. 답은 아니오입니다. 송신 측과 수신 측 사이에는 남의 통신도 흘러드는 공유 네트워크가 있습니다. 도중 라우터의 처리 능력을 넘은 분량은 큐에 쌓이고, 큐가 넘치면 버려집니다. 전원이 전력으로 보내면, 재전송이 재전송을 부르고, 네트워크 전체가 막혀 누구의 데이터도 흐르지 않게 됩니다(혼잡 붕괴 — 1980 년대에 실제로 일어난 사고입니다).
골치 아픈 것은, 경로의 빈 용량은 아무도 알려 주지 않는다는 점입니다. rwnd 같은 신고는 없습니다. 그래서 TCP 는 약속 그 3 의 후반으로서, 송신 측이 자력으로 경로의 용량을 추정하는 혼잡 제어를 갖습니다. 추정값은 또 하나의 창 — 혼잡 윈도우(cwnd) — 로서 갖고, 실제로 보내도 좋은 양은 min(rwnd, cwnd) 로 정해집니다. 상대의 사정과 길의 사정, 엄격한 쪽에 맞추는 것입니다.
cwnd 의 일생. 배증으로 달려 올라가고(슬로 스타트), 임계값부터는 한 걸음씩(혼잡 회피), 로스로 양보하고 다시 올라간다. 삐죽삐죽한 톱니는 TCP 가 건전하게 한계를 계속 탐색하고 있는 모습입니다.
탐색 방식 — 배증으로 달리고, 한 걸음씩 좁힌다
용량을 알 수 없는 길에서는, 적게 시작해서 상황을 보며 늘려 갈 수밖에 없습니다. TCP 의 탐색 방식은 2 단 변속입니다.
그리고 경로의 한계를 넘으면 패킷 로스가 일어나, 제 3 장의 감지가 작동합니다. 여기서 증거의 무게에 따라 벌을 바꾸는 것이 TCP 의 지혜입니다.
늘리고, 부딪히고, 양보하고, 다시 늘린다. cwnd 의 그래프가 그리는 톱니 모양의 파도는, TCP 가 네트워크의 용량을 모두가 나누어 갖기 위해 계속 지불하고 있는 인사 같은 것입니다. 누군가가 양보하지 않는 설계였다면, 인터넷은 성립하지 않았을 것입니다.
소문제 5-1 — 2 개의 브레이크를 구별한다
「누구의 사정에 맞추고 있는가」로 2 개의 창을 구별해 말할 수 있게 합니다.
Q1. 흐름 제어와 혼잡 제어의 차이에 대한 설명으로 올바른 것은 어느 것입니까.
맞추는 상대가 다릅니다. 흐름 제어(제 4 장의 rwnd)는 수신 측이 신고해 오는 버퍼의 빈 공간. 혼잡 제어(이 장의 cwnd)는 아무도 신고해 주지 않는 경로의 혼잡을 송신 측이 자력으로 추정한 결과입니다. 수신 측은 멀쩡해도 네트워크가 혼잡할 수도, 그 반대일 수도 있으므로, 독립된 2 개의 브레이크가 필요합니다.
Q2. TCP 가 패킷 로스를 「혼잡의 신호」로 사용하는 것은 왜입니까.
경로의 라우터는 기본적으로 아무것도 알려 주지 않습니다(명시적인 통지 구조도 있지만, 항상 의지할 수는 없습니다). 하지만 혼잡의 물리적인 귀결로서, 넘친 큐는 패킷을 버립니다. 그래서 「로스가 일어났다 = 어딘가의 큐가 넘칠 지경」이라는 추리가 성립하는 것입니다. 로스에는 전파 품질 등 혼잡 이외의 원인도 있으므로 완벽한 신호는 아니지만, 추가 협력 없이 사용할 수 있는 가장 보편적인 신호입니다.
Q3. 송신 측이 어느 순간에 보내도 좋은 데이터 양(in-flight 의 상한)을 정하는 것은 어느 것입니까.
2 개의 브레이크는 동시에 작동합니다. 상대의 버퍼(rwnd)와 경로의 추정 용량(cwnd), 어느 쪽이든 엄격한 쪽 = min(rwnd, cwnd) 가 실제 상한입니다. 제 4 장의 「상한 = 윈도우 ÷ RTT」 식의 윈도우도, 정확히는 이 min 의 값입니다. 한쪽만 크게 해도, 다른 한쪽이 작으면 스루풋은 늘지 않습니다.
실무에서 어떻게 보이는가
혼잡 제어는 평소에는 투명하지만, 성능 문제에서는 반드시 얼굴을 내밉니다.
- 접속 직후에는 누구나 느리다 — 아무리 굵은 회선이라도 cwnd = 1 MSS 근처부터 탐색을 시작하므로, 짧은 통신은 한 번도 최고 속도에 도달하지 않습니다. 작은 요청을 대량으로 다시 맺기보다, 1 개의 커넥션을 재사용하는(keep-alive) 편이 빠른 이유가 이것입니다.
- 로스 1 회의 비용은 「재전송 1 개」로 끝나지 않는다 — cwnd 가 절반(또는 1)으로 떨어지므로, 스루풋의 회복에 몇 RTT 나 걸립니다. 로스율 1% 는 「1% 느려진다」가 아니라, 자릿수가 다르게 느려질 수 있습니다.
- 속도 상한 정체에는 3 명의 용의자 — rwnd(상대의 버퍼), cwnd(경로의 혼잡), 그리고 대역폭 그 자체. 제 4 장의 식에 대입하여, 어느 창이 min 을 차지하고 있는지를 생각하는 것이 구분의 정석입니다.
소문제 5-2 — cwnd 의 성장을 손으로 계산한다
cwnd 는 세그먼트 수(MSS 단위)로 셉니다. 배증과 덧셈, 2 가지 성장을 계산해 주세요.
Q4. 슬로 스타트로 cwnd = 1 MSS 부터 송신을 시작했습니다. 1 RTT 마다 cwnd 가 2 배가 될 때, 3 RTT 경과 후의 cwnd 는 몇 MSS 입니까.
1 → 2 → 4 → 8 로, 3 RTT 후는 8 MSS 입니다. 「슬로」라는 이름과 달리 성장은 지수적 — 1 RTT 에 보낸 세그먼트의 ACK 가 1 개 돌아올 때마다 cwnd 를 1 늘리면, 결과적으로 매 RTT 마다 배가 됩니다. 10 RTT 면 1024 MSS 에 달하는 기세로, 비어 있는 회선이라면 금방 다 쓸 수 있는 속도입니다.
Q5. 임계값(ssthresh)을 넘어 혼잡 회피 모드에 들어가, cwnd = 10 MSS 부터 1 RTT 마다 1 MSS 씩 늘리고 있습니다. 5 RTT 경과 후의 cwnd 는 몇 MSS 입니까.
10 + 5 = 15 MSS 입니다. 한계가 가깝다고 알고 나서도 배증을 계속하면, 초과한 순간에 대량의 로스를 일으킵니다. 그래서 위험 수역에서는 덧셈(1 RTT 에 +1 MSS)으로 전환하여, 살며시 한계를 탐색합니다. 멀리서는 배증으로 달리고, 한계 가까이에서는 한 걸음씩 — 이 2 단 변속이 TCP 의 탐색 방식입니다.
Q6. 로스를 감지했을 때의 TCP 의 반응은 감지 방식에 따라 다릅니다. 올바른 조합은 어느 것입니까. (제 3 장의 2 가지 감지 방법을 떠올려 주세요)
증거의 무게에 벌의 무게를 맞춥니다. 중복 ACK 3 회(빠른 재전송)는 「후속은 도착하고 있다 = 경로는 살아 있다」는 경증의 신호이므로, cwnd 를 절반으로 낮추고 계속 달립니다. 타임아웃은 「아무것도 돌아오지 않는다」는 중증의 신호이므로, cwnd = 1 MSS 까지 되돌려 슬로 스타트부터 다시 합니다. 캡처에서 스루풋의 낭떠러지를 보면, 어느 쪽의 감지가 방아쇠였는지를 의심한다 — 이것이 제 7 장에서 쓰는 읽기 방식입니다.
이 장에서 가져갈 것
- 흐름 제어는 상대의 사정, 혼잡 제어는 네트워크의 사정. 보내도 좋은 양은 min(rwnd, cwnd)
- 경로의 용량은 신고되지 않으므로, 패킷 로스를 혼잡의 신호로 삼아 자력으로 탐색한다
- 성장은 2 단 변속 — 슬로 스타트는 매 RTT 마다 배증, 혼잡 회피는 매 RTT 마다 +1 MSS
- 벌도 2 단계 — 중복 ACK 3 회로 절반, 타임아웃으로 1 로 되돌려 다시 시작. 톱니의 파도는 건전한 양보의 모습
이것으로 3 가지 약속이 모두 갖춰졌습니다. 다음 장은 시점을 앱 측으로 옮깁니다. TCP 가 완벽하게 약속을 지키고 있는데도, 앱의 데이터가 「빠지고·붙는」 것처럼 보인다 — 실무에서 가장 많은 TCP 의 오해, 바이트 스트림과 프레이밍의 이야기입니다.