혼잡 제어와 지연 - BDP, 버퍼블로트, 처리량과 지연의 교환
대역폭은 넉넉한데 파일 전송이 느리다. 다운로드를 시작하면 다른 요청의 지연이 함께 튄다. 둘 다 “네트워크가 느리다”로 뭉뚱그려지지만 원인이 다르고, 혼잡 제어가 무엇을 하는지 알면 갈린다.
대역폭이 남아도 느린 이유: BDP
TCP는 ACK를 받기 전에 보낼 수 있는 양(윈도)이 정해져 있다. 그 윈도가 작으면 한 왕복시간(RTT)마다 윈도 크기만큼만 보내게 된다.
1
처리량 ≈ 윈도 크기 / RTT
필요한 윈도 크기가 대역폭-지연 곱(BDP) 이다.
1
2
BDP = 대역폭 × RTT
100 Mbps × 100 ms = 1.25 MB
서울-미국 간 RTT가 150ms이고 1Gbps 회선이면 BDP는 약 18MB다. 윈도가 그보다 작으면 회선이 아무리 굵어도 그만큼만 나온다. “대역폭이 남는데 느리다”의 전형적인 원인이고, 장거리 전송에서 특히 그렇다.
윈도는 두 가지가 제한한다. 수신자가 광고하는 수신 윈도(버퍼 크기, window scaling 옵션이 필요)와 송신자가 계산하는 혼잡 윈도. 실제 윈도는 둘 중 작은 쪽이다.
혼잡 제어가 하는 일
네트워크가 얼마나 받아 줄 수 있는지는 아무도 알려 주지 않는다. TCP는 보내 보고 반응해서 추정한다.
전통적인 알고리즘(Reno, CUBIC)은 패킷 손실을 혼잡 신호로 쓴다. 손실이 없으면 윈도를 키우고, 손실이 나면 줄인다. CUBIC이 리눅스 기본값이고, 큰 BDP에서도 빨리 회복하도록 설계됐다.
여기에 구조적 문제가 있다. 손실이 나야 혼잡을 안다는 것은, 어딘가의 버퍼가 가득 찰 때까지 계속 보낸다는 뜻이다.
버퍼블로트
라우터와 스위치에는 버퍼가 있다. 버퍼가 크면 패킷을 덜 버리므로 좋아 보인다. 실제로는 반대다.
손실 기반 혼잡 제어는 손실이 날 때까지 보내므로, 버퍼를 끝까지 채운다. 버퍼가 크면 그 안에서 기다리는 시간이 길어진다. 패킷은 안 버려지고 대신 수백 ms씩 지연된다.
결과가 이것이다. 대용량 전송 하나가 같은 경로의 모든 트래픽 지연을 끌어올린다. 다운로드 중에 화상 통화가 끊기고, 백업 작업이 도는 동안 API 응답이 느려진다. 처리량은 정상인데 지연만 나빠지므로 대역폭 지표로는 안 보인다.
대책은 두 방향이다.
- 큐 관리: CoDel, FQ-CoDel처럼 큐에 오래 머문 패킷을 미리 버리거나 흐름별로 큐를 나눈다. 라우터 쪽의 답이다.
- 다른 혼잡 신호: BBR은 손실 대신 대역폭과 RTT를 추정해 버퍼를 채우지 않는 지점에서 멈춘다. 버퍼블로트를 만들지 않고, 손실이 있는 장거리 경로에서 CUBIC보다 처리량이 좋은 경우가 많다.
애플리케이션에서 할 수 있는 것
혼잡 제어는 커널의 일이지만 애플리케이션의 선택이 영향을 준다.
연결 재사용. 새 연결은 slow start부터 시작한다. 윈도가 작은 상태에서 출발하므로 짧은 요청일수록 손해가 크다. keep-alive와 커넥션 풀이 이 비용을 없앤다(TCP 연결 수립과 큐).
작은 쓰기 합치기. Nagle 알고리즘은 작은 쓰기를 모아 보내지만, 지연 ACK와 만나면 수백 ms가 붙는다. 대화형 프로토콜에서 TCP_NODELAY를 켜는 이유다. 대신 애플리케이션이 직접 합쳐 보내야 한다.
큰 전송을 분리. 백업, 로그 전송, 대용량 업로드를 지연에 민감한 트래픽과 같은 경로에 두면 버퍼블로트가 그쪽을 때린다. 대역폭 제한이나 경로 분리가 답이다.
이 설명이 깨지는 곳
- 데이터센터 내부는 RTT가 작아 BDP가 거의 문제가 되지 않는다. 이 글의 내용이 실제로 쓰이는 곳은 인터넷 경로, 리전 간 통신, 모바일이다.
- HTTP/2의 멀티플렉싱은 TCP 레벨 HOL 블로킹을 해결하지 못한다. 한 연결에 스트림을 몰면 패킷 하나가 손실됐을 때 전부 기다린다(HTTP/1.1, 2, 3).
- 클라우드는 인스턴스 타입별로 대역폭 상한이 있다. 혼잡 제어 이전에 그 상한을 먼저 확인해야 한다.
- BBR이 항상 낫지는 않다. CUBIC과 섞여 있을 때의 공정성 문제가 지적돼 왔다.
무엇을 재면 확인되는가
iperf3로 처리량을 재고 RTT와 곱해 실효 윈도를 역산한다. BDP에 못 미치면 윈도가 제한이다.- 대용량 전송을 시작하고 같은 경로의 짧은 요청 지연을 동시에 본다. 버퍼블로트가 있으면 즉시 보인다.
ss -i로 연결별cwnd,rtt, 재전송 수를 본다. 손실이 실제로 나고 있는지 확인된다.- 혼잡 제어를 CUBIC과 BBR로 바꿔 가며 1번과 2번을 반복한다.
2번이 가장 인상적이다. “대역폭은 남는데 왜 느리지”가 그래프 하나로 설명된다.
실무와의 접점
monticker의 WebSocket과 폴링 비교는 같은 머신에서 잰 수치라 이 층의 영향이 거의 없었다. 그 글의 한계에 “같은 머신에서 k6가 부하를 만들었다”를 적은 것이 그 뜻이다. 실제 네트워크를 지나는 순간 conflation 주기와 메시지 크기의 의미가 달라진다. 작은 메시지를 자주 보내는 설계는 RTT가 큰 경로에서 다르게 동작하고, 그것은 로컬에서 잴 수 없다.
정리
- 처리량은 윈도 크기를 RTT로 나눈 값이다. 윈도가 BDP보다 작으면 대역폭이 남아도 못 쓴다.
- 손실 기반 혼잡 제어는 버퍼가 찰 때까지 보낸다. 버퍼가 크면 지연이 길어진다.
- 버퍼블로트는 대용량 전송 하나가 같은 경로 전체의 지연을 올리는 현상이다. 대역폭 지표로는 안 보인다.
- 대책은 큐 관리(CoDel)와 다른 혼잡 신호(BBR)다.
- 애플리케이션은 연결 재사용, 쓰기 합치기, 큰 전송의 경로 분리로 기여한다.
- 데이터센터 내부에서는 이 문제들이 거의 드러나지 않는다.
댓글
아직 댓글이 없습니다