TCP 흐름과 혼잡 제어를 운영 관점에서 읽기
TCP의 흐름 제어와 혼잡 제어를 TTFB, 클라우드 통신, 장애 분석 관점에서 정리합니다. cwnd, rwnd, rtt, retrans 같은 신호를 바탕으로 병목을 분리하고, Cubic과 BBR 선택 시 봐야 할 실패 조건까지 다룹니다.
Script Companion
오디오와 함께 스크립트 보기
- 01
TCP 흐름 제어와 혼잡 제어는 단순히 네트워크 이론에 머물지 않습니다. SSR 서버가 클라이언트에게 첫 바이트를 보내는 속도, 즉 TTFB는 TCP의 초기 혼잡 윈도우 cwnd에 직접 영향을 받습니다. 서버 코드가 충분히 빨라도 TCP가 느리게 시작하면 사용자는 빈 화면을 보게 됩니다. AWS EC2 간 통신, EKS Pod 간 통신, RDS 연결도 TCP 위에서 동작하므로, 혼잡 제어 알고리즘과 소켓 버퍼 설정은 throughput과 latency에 바로 연결됩니다.
- 02
장애를 볼 때는 먼저 두 가지 윈도우를 구분해야 합니다. 수신 윈도우 rwnd는 받는 쪽 애플리케이션과 버퍼가 얼마나 더 받을 수 있는지를 보여주고, 혼잡 윈도우 cwnd는 중간 네트워크 경로가 얼마나 더 보낼 수 있는지를 제한합니다. cwnd가 rwnd보다 작으면 네트워크 중간 경로 병목을 의심하고, rwnd가 cwnd보다 작으면 수신 측 애플리케이션이나 버퍼 병목을 의심합니다. ss -ti 출력에서 retrans, rtt, cwnd 값을 읽는 이유도 이 구분을 하기 위해서입니다.
- 03
알고리즘 선택은 BBR이 더 최신이니 켠다는 식으로 결정하면 안 됩니다. 먼저 현재 병목이 손실 기반 회복 지연인지, 큐잉 지연인지, 아니면 수신자나 애플리케이션 병목인지 분리해야 합니다. ss -ti에서 cwnd가 낮고 rtt가 안정적인데 장거리 전송만 느리면 고BDP 회복 문제가 의심됩니다. 반대로 send는 충분한데 rcv_space가 작거나 애플리케이션 read가 늦으면 혼잡 제어를 바꿔도 효과가 없습니다.
- 04
환경별 선택 기준도 이 분리 위에 놓입니다. 데이터센터 내부처럼 RTT가 1ms보다 작은 환경에서는 Cubic이 기본값으로 권장되고, BBR의 PROBE_RTT가 오히려 비효율일 수 있습니다. 리전 간처럼 RTT가 10에서 100ms인 경로에서는 BBR이 고BDP에서 throughput 향상에 유리합니다. 모바일이나 무선 환경에서는 노이즈 손실을 혼잡으로 오해하지 않기 위해 BBR을 고려하고, RTT가 500ms를 넘는 위성 통신에서는 Cubic의 선형 회복이 너무 느릴 수 있어 BBR이 권장됩니다.
- 05
BBR을 켤 때는 실패 시나리오를 같이 봐야 합니다. ACM Queue의 BBR 설명은 token-bucket policer가 있는 경로에서 ProbeBW의 고 gain 구간이 지속적인 중간 손실을 만들 수 있다고 설명합니다. 그래서 BBR canary는 처리량만 보면 부족합니다. 같은 시간대의 retrans, p95와 p99 RTT, 그리고 상대 Cubic 플로우의 처리량을 함께 봐야 합니다. 문서의 운영 가드레일 추정으로는 전환 후 처리량이 10% 올라도 p99 RTT가 2배가 되거나 같은 링크의 Cubic 트래픽이 굶으면 rollback 대상으로 둡니다.
- 06
재전송에서도 신호를 읽는 방식이 중요합니다. Fast Retransmit가 일반 타임아웃 재전송보다 빠른 이유는 3-dup ACK가 뒤의 패킷이 도달하고 있다는 신호이기 때문입니다. 즉 완전히 끊긴 상태로 타임아웃이 끝나기를 기다리는 것이 아니라, 일부 패킷이 빠졌지만 그 뒤의 패킷 흐름은 관측된 상태에서 즉시 재전송할 수 있습니다. 이 차이를 알아야 retrans 증가가 단순 손실인지, 회복 지연인지, 또는 더 큰 병목의 일부인지 판단할 수 있습니다.
- 07
실무 장애에서는 TCP 연결의 수명과 타임아웃 설정이 그대로 드러납니다. Node.js SSR 서버를 AWS ALB 뒤에 둘 때 Node.js HTTP 서버의 기본 keepAliveTimeout은 5000ms, 즉 5초입니다. ALB의 기본 유휴 타임아웃은 60초라서, ALB는 연결이 살아있다고 보고 새 요청을 보낼 수 있습니다. 그런데 서버가 이미 해당 TCP 연결을 종료한 상태라면 서버가 RST를 보내고, ALB는 502를 반환합니다. 이 문제는 서버가 먼저 연결을 끊는 타이밍과 ALB가 연결을 재사용하는 판단이 어긋나서 생깁니다.
- 08
또 다른 장애는 TIME_WAIT 포트 고갈입니다. 단기 연결을 많이 생성하는 서비스, 예를 들어 커넥션 풀 없이 DB에 직접 연결하거나 외부 API 호출을 반복하는 경우 TIME_WAIT가 쌓입니다. TIME_WAIT는 2MSL, 약 60초 동안 포트를 점유하므로 로컬 포트가 고갈될 수 있고, 그 결과 connection refused: no ephemeral port available 같은 증상이 나타납니다. 여기서 핵심은 혼잡 제어 알고리즘이 아니라 연결 생성 방식과 포트 점유 시간입니다.
- 09
리전 간 파일 전송이 느릴 때는 BDP와 cwnd를 함께 봐야 합니다. 문서의 예시는 BDP가 10Gbps 곱하기 32ms로 40MB인데, cwnd는 45 MSS, 대략 65KB에 불과한 상황입니다. 이 경우 소켓 버퍼 크기가 BDP보다 훨씬 작아 네트워크 파이프를 채우지 못합니다. 마지막으로 Nagle 알고리즘과 Delayed ACK가 함께 켜져 있으면 MySQL 프로토콜처럼 요청이 여러 TCP 세그먼트로 분할될 때 40ms 지연이 생길 수 있습니다. 서버는 Delayed ACK로 기다리고, 클라이언트는 Nagle로 ACK를 기다리면서 교착이 생기는 구조입니다.
같은 레이어