들어가며: 인터넷이 살아있을 수 있는 이유
1986년 10월 — 인터넷이 죽을 뻔했다
1986년 10월, 초기 인터넷에서 이상한 일이 벌어졌다. UC 버클리와 로렌스 버클리 연구소 사이의 처리량이 갑자기 32 kbit/s에서 40 bit/s로 떨어졌다. 1000분의 1이다.
원인은 혼잡 붕괴(Congestion Collapse) 였다. 모든 송신자가 동시에 너무 많이 보내 네트워크가 포화되고, 패킷이 손실되고, 재전송이 또 포화를 가중시키는 악순환. 인터넷이 처음으로 "멈추는" 경험을 한 순간이었다.
Van Jacobson의 구원
당시 로렌스 버클리의 연구원이었던 Van Jacobson은 이 문제를 풀기 위해 TCP에 혼잡 제어(Congestion Control) 를 도입했다. 1988년 발표한 논문 "Congestion Avoidance and Control"은 오늘날까지 네트워킹의 기초가 되었다.
핵심 아이디어:
- 송신자는 네트워크의 용량을 모른다.
- 그러나 점진적으로 탐색할 수 있다.
- 패킷 손실이 발생하면 급격히 줄인다.
- 다시 천천히 늘린다.
이 단순한 규칙이 인터넷을 살렸다. TCP Reno, TCP Tahoe 이름으로 알려진 초기 알고리즘이다.
40년 후: Google의 답
2016년 Google은 BBR (Bottleneck Bandwidth and RTT) 이라는 새로운 혼잡 제어를 발표했다. 벤치마크 결과:
- YouTube 처리량: 전 세계 평균 4% 증가. 일부 지역 14% 증가.
- 특정 링크: 기존 대비 2,700배 처리량 향상.
- RTT (지연): 기존 대비 대폭 감소.
2,700배는 오타가 아니다. 특정 고지연 고손실 환경에서 CUBIC은 거의 동작하지 않았지만 BBR은 정상적으로 작동했다.
이 글에서는 TCP 혼잡 제어의 40년 진화를 따라간다. Reno → CUBIC → BBR → BBRv2. 각 알고리즘이 어떤 문제를 풀었고, 어떤 한계를 가졌고, 다음 세대가 어떻게 해결했는지.
1. 혼잡 제어의 근본 문제
"얼마나 빨리 보낼 수 있나?"
송신자의 질문: "네트워크가 수용할 수 있는 최대 속도는 얼마인가?"
정답은 존재한다. 병목 링크(bottleneck link) 의 대역폭이다. 하지만 송신자는 그것을 모른다:
- 경로를 이루는 라우터들의 용량 모름.
- 다른 흐름들이 얼마나 쓰고 있는지 모름.
- 네트워크 상태는 초 단위로 변한다.
그래서 송신자는 간접 신호로부터 추론해야 한다:
- 패킷 손실: 큐가 넘쳤다는 신호.
- RTT (Round-Trip Time): 큐잉 지연을 반영.
- ACK 도착 속도: 실제 전달 속도.
각 혼잡 제어 알고리즘은 어떤 신호를 어떻게 해석하느냐로 구분된다.
두 가지 목표: 효율과 공정성
좋은 혼잡 제어는 두 가지를 동시에 달성해야 한다:
- 효율성(Efficiency): 네트워크 자원을 최대한 활용.
- 공정성(Fairness): 여러 흐름이 대역폭을 공평하게 나눔.
이 둘이 때론 상충한다. BBR이 논란이 되는 이유 중 하나는 "CUBIC과 함께 있을 때 공정한가?"라는 질문이다.
BDP: 대역폭-지연 곱
핵심 개념 하나: BDP (Bandwidth-Delay Product).
BDP = Bandwidth × RTT
이것은 "네트워크 파이프에 담을 수 있는 최대 바이트 수"다. 100 Mbit/s 링크에 100ms RTT면:
BDP = 100 × 10^6 / 8 × 0.1 = 1.25 MB
송신자가 1.25MB를 미리 보내고 ACK를 기다리면 링크를 완전히 활용한다. 이것보다 적으면 링크가 놀고, 많으면 큐잉 지연이 늘어난다.
좋은 혼잡 제어의 목표: BDP만큼 in-flight 데이터 유지.
2. TCP Reno: 손실 기반의 조상
AIMD: Additive Increase, Multiplicative Decrease
TCP Reno (그리고 Tahoe)의 핵심은 AIMD다:
- Additive Increase: 순조롭게 흐르면 매 RTT마다 cwnd를 1 패킷씩 증가.
- Multiplicative Decrease: 패킷 손실 시 cwnd를 절반으로 감소.
cwnd (congestion window)는 송신자가 ACK를 기다리지 않고 보낼 수 있는 최대 바이트.
cwnd
│
│ /\ /\ /\
│ / \ / \ / \ ← 톱니 모양
│ / \/ \/ \
│ /
│/
└─────────────────→ 시간
이 톱니 모양이 TCP의 상징이다.
Slow Start: 처음엔 빠르게
연결 시작 시엔 매우 빠르게 늘린다 (Slow Start):
- 매 ACK마다 cwnd 2배씩 증가 (지수 성장).
- ssthresh (slow start threshold)에 도달하면 선형 성장(AIMD)으로 전환.
이름이 "Slow" Start지만 실제로는 가장 빠른 성장이다. 초기 Reno의 ssthresh가 너무 커서 선형 구간이 길어, 상대적으로 slow start가 짧아 보였던 것.
Fast Retransmit & Fast Recovery
패킷 손실 감지:
- Timeout: ACK가 오지 않음 (최후의 수단).
- Duplicate ACK: 같은 ACK가 3번 도착 → 특정 패킷이 손실된 것으로 추정.
Tahoe는 손실 시 cwnd = 1로 리셋하고 slow start 재시작. Reno는 cwnd = cwnd/2로 감소하고 congestion avoidance 계속 (더 부드러움).
Reno의 한계: 고대역폭 고지연
Reno는 1990년대 네트워크엔 충분했다. 그러나 네트워크가 발전하면서 문제가 드러났다:
- 10 Gbit/s 링크에 100ms RTT → BDP = 125 MB.
- Reno는 매 RTT마다 1 MSS(~1500바이트) 증가.
- cwnd가 BDP에 도달하려면 수만 RTT 필요.
- 실제론 중간에 패킷 손실로 반복 절반 감소.
- 결과: 링크 이용률이 매우 낮음.
고속 링크 시대에 Reno는 구시대의 유물이 되어 갔다.
3. TCP CUBIC: 고대역폭을 위한 재설계
문제 정의
2008년 이남석 등이 제안한 CUBIC은 다음 질문에서 출발한다:
"cwnd의 성장 곡선을 RTT와 독립적으로 만들 수 없을까?"
Reno는 RTT에 성장 속도가 비례한다. RTT가 긴 연결은 영원히 작다. CUBIC은 시간(초) 기반으로 성장한다.
3차 함수 기반 성장
CUBIC은 cwnd를 다음 공식으로 성장시킨다:
W(t) = C(t - K)³ + W_max
- W_max: 마지막 손실 시점의 cwnd.
- K: W_max에 도달하는 데 걸리는 시간.
- C: 상수 (기본 0.4).
이 함수의 특성:
- 손실 직후엔 느리게 성장 (이전 최대 근처에서 안정).
- W_max 근처에 도달하면 천천히 probing.
- 넘어서면 빠르게 성장 (새로운 최대 탐색).
cwnd
│ ___
│ ____/
│ ___/
│ ___/
│ ___/
│ __/
│/
└──────────────────→ 시간
loss W_max 위로 탐색
RTT 독립성
CUBIC은 시간(wall clock) 기반으로 성장하므로 RTT에 영향받지 않는다. 100ms RTT나 300ms RTT나 같은 속도로 커진다. 이 덕분에 원거리 연결에서도 효율적.
Linux 기본
CUBIC은 Linux 2.6.19부터 기본 혼잡 제어가 되었다 (2006). Windows는 Vista부터 Compound TCP를 썼고 Windows 10에서 CUBIC으로 전환.
사실상 2010년대 인터넷 트래픽의 90%+가 CUBIC을 사용했다.
CUBIC의 한계
CUBIC도 만능은 아니다:
- 여전히 손실 기반: 큐가 넘치기 전까지 "아직 늘려도 돼"라고 해석.
- Bufferbloat: 라우터 큐가 클수록 RTT 늘어남 → 지연 문제.
- Wi-Fi와 모바일 네트워크: 패킷 손실이 혼잡 때문이 아닐 수 있음.
- Shallow buffer 링크: 짧은 버스트로 성능 저하.
이 문제들이 BBR의 등장으로 이어진다.
4. Bufferbloat: 숨은 살인자
문제
2010년 Jim Gettys가 발견한 bufferbloat 문제. 현대 라우터는 거대한 버퍼를 가진다 (수십 MB까지). 이는 "패킷 손실을 줄이기 위한" 선의의 설계지만, 끔찍한 부작용이 있다:
- 손실 기반 TCP(Reno, CUBIC)는 큐가 다 찰 때까지 데이터를 더 보낸다.
- 큐에 쌓인 데이터의 RTT는 엄청나게 늘어난다.
- 인터랙티브 트래픽(VoIP, 게임)이 이 큰 큐 뒤에서 기다린다.
- 결과: 핑이 수 초까지 치솟음.
실험
# 대용량 다운로드 시작
wget https://big-file.example.com/10gb.bin &
# 같은 네트워크에서 핑 측정
ping 8.8.8.8
# 이전: 20 ms
# 다운로드 중: 500 ms ~ 2000 ms! ← bufferbloat
집 Wi-Fi에서 흔히 경험하는 현상이다. Netflix 스트리밍 중엔 게임이 랙 걸리는 이유.
AQM: 큐 관리의 부활
해결책의 일부는 AQM (Active Queue Management):
- CoDel (Controlled Delay): 큐 대기 시간 기반 드롭.
- FQ_CoDel: Fair Queuing + CoDel.
- PIE (Proportional Integral Enhanced): 수학적 제어 이론 적용.
이런 알고리즘은 큐가 가득 차기 전에 몇 개 패킷을 드롭(또는 ECN 표시)해서 송신자에게 신호를 보낸다. Linux 커널은 CoDel을 기본 스케줄러로 도입.
하지만 네트워크 전체를 업그레이드하는 건 어렵다. 엔드포인트에서 해결하는 방법이 더 실용적이다. 그것이 BBR이다.
5. BBR: Google의 혁명
BBR의 통찰
Google의 Neal Cardwell 등이 2016년 발표한 BBR (Bottleneck Bandwidth and RTT) 은 완전히 새로운 접근이다:
"손실을 기다리지 말고, 네트워크의 진짜 대역폭과 지연을 측정하자."
기존 알고리즘은 손실을 혼잡의 신호로 해석했다. BBR은 대역폭과 RTT를 직접 추정한다:
- max bandwidth (BtlBw): 최근 측정된 최대 전송 속도.
- min RTT (RTprop): 최근 측정된 최소 RTT (큐잉 없는 지연).
이 둘로부터 BDP를 계산하고, BDP 근처에서 유지한다. 큐가 쌓이지 않고 링크가 가득 찬다.
동작 개요
BBR은 네 가지 상태를 순회한다:
- STARTUP: 연결 시작. cwnd를 빠르게 늘려 BtlBw 탐색.
- DRAIN: STARTUP에서 큐가 쌓였으므로 비우기.
- PROBE_BW: 정상 운영. 주기적으로 약간 더 빠르게 보내 BtlBw 재측정.
- PROBE_RTT: 8초마다 200ms 동안 cwnd를 크게 줄여 RTprop 재측정.
Pacing Rate
BBR의 중요한 특징: pacing rate 기반 전송. cwnd가 아니라 초당 몇 바이트를 보낼지를 직접 제어한다.
pacing_rate = pacing_gain × BtlBw
pacing_gain = 1.0→ BtlBw대로 전송 (정상).pacing_gain = 1.25→ 더 빠르게 (probing).pacing_gain = 0.75→ 더 느리게 (draining).
Linux 커널의 FQ (Fair Queue) scheduler가 pacing을 구현한다. BBR을 쓰려면 FQ도 함께 설정해야 한다.
대역폭 측정
BBR은 각 ACK에서 delivery rate를 측정한다:
delivery_rate = delivered_bytes / elapsed_time
delivered_bytes: ACK된 바이트.elapsed_time: 그 바이트들이 보내진 시간 구간.
최근 10 RTT 동안의 최대 delivery rate를 BtlBw로 사용. 이는 "우리가 얻을 수 있는 최고 속도"를 추정한다.
RTT 측정
RTprop은 "큐가 비었을 때의 RTT"다. BBR은 최근 10초 동안의 최소 RTT를 추정치로 사용.
RTprop이 10초 이상 업데이트되지 않으면 PROBE_RTT 상태로 전환: cwnd를 4 MSS로 크게 줄여 큐를 비우고 새 RTT를 측정.
BBR의 성과
Google의 발표 (2016):
- YouTube 처리량: 평균 4% 증가, 일부 지역 14%.
- 재버퍼링: 영상 스트림의 중간 정지 크게 감소.
- RTT: CUBIC 대비 절반 이하로 감소 (큐잉 제거 효과).
이후 Google은 모든 google.com 서비스에 BBR을 적용했다. 전 세계 트래픽의 수십 %가 BBR로 처리되고 있다.
6. BBR의 논란: 공정성 문제
연구자들의 경고
BBR 발표 후 네트워킹 커뮤니티에서 논란이 있었다:
"BBR이 손실 기반 TCP(CUBIC)와 공유될 때 공정한가?"
실험 결과: 특정 상황에서 BBR은 CUBIC 흐름의 대역폭을 불공평하게 점유할 수 있었다. 그 이유:
- BBR은 손실에 신경 쓰지 않음.
- CUBIC은 손실 시 절반으로 줄임.
- BBR이 큐를 채우면 CUBIC 흐름은 손실을 겪고 양보.
- BBR은 그 양보를 "더 보내도 돼"로 해석.
CoDel의 역설
재미있게도 AQM 없는 큰 버퍼 링크에서 BBR은 매우 공정하다. AQM이 있는 작은 버퍼 링크에서 BBR이 CUBIC을 압도할 수 있다. 정확한 조건은 여전히 연구 중이다.
실험: 네 흐름 시나리오
10 Mbit/s 링크, 100ms RTT, 4개 동시 흐름:
시나리오 A: 4 CUBIC
→ 각 ~2.5 Mbit/s (공평)
시나리오 B: 4 BBR
→ 각 ~2.5 Mbit/s (공평)
시나리오 C: 2 CUBIC + 2 BBR
→ BBR 각 4 Mbit/s, CUBIC 각 1 Mbit/s (불공평)
실제 인터넷에선 CUBIC이 여전히 많으므로 이 이슈는 중요하다.
7. BBRv2: 공정성의 회복
BBRv2의 목표
Google은 BBR의 공정성 문제를 인지하고 BBRv2를 개발했다:
- 손실 시 반응: 적절히 손실을 존중.
- ECN 지원: Explicit Congestion Notification 활용.
- CUBIC과의 공존: 더 나은 친화력.
주요 변경
- Inflight Cap: 미리 정해진 상한을 두어 큐가 쌓이는 걸 제한.
- Loss 기반 모드: 손실이 발생하면 일부 반응.
- ECN Response: ECN CE 마킹을 받으면 cwnd 감소.
- Probe 주기 조정: PROBE_BW 사이클을 유연하게.
성능
BBRv2는 BBRv1 대비 약간의 처리량 희생이 있지만 공정성이 크게 개선된다:
- CUBIC과 공존 시 대역폭을 거의 1:1로 나눔.
- 손실률이 약간 감소.
- 재버퍼링 이벤트 개선.
Google은 BBRv2로 전환했으며, 일부 서비스에서 BBRv3를 테스트 중이다.
8. 다른 알고리즘들
TCP Vegas: RTT 기반의 선구자
1994년 발표된 Vegas는 BBR의 선구자다. RTT 증가를 혼잡 신호로 사용:
expected_throughput = cwnd / baseRTT
actual_throughput = cwnd / currentRTT
if actual < expected - α: increase cwnd
if actual > expected - β: decrease cwnd
문제: Reno와 공존 시 불공평. Reno가 공격적이어서 Vegas 흐름을 눌러버린다. 그래서 널리 쓰이지 못했다.
BBR이 성공한 이유 중 하나는 Vegas의 아이디어를 현대에 맞게 재설계했다는 점이다.
TCP Westwood: 무선을 위한
무선 네트워크에선 패킷 손실이 혼잡이 아닌 비트 오류로 발생할 수 있다. Westwood는 대역폭 추정을 통해 이를 구분하려 시도. 모바일 시장에서 일부 사용.
H-TCP: 데이터센터용
고대역폭 환경을 위한 CUBIC 대안. 일부 데이터센터 환경에서 사용되었으나 CUBIC과 BBR에 밀려 사라짐.
DCTCP: 데이터센터 TCP
Microsoft가 개발한 DCTCP는 ECN 마킹을 정밀하게 해석:
- 라우터가 큐 길이에 따라 ECN CE 마킹.
- 송신자는 CE 비율에 비례해 cwnd 감소.
- 부드러운 반응으로 낮은 지연.
데이터센터 내부 트래픽에 최적화. 공용 인터넷엔 부적합 (ECN 지원 필요).
BBRv3 (2024): 현재의 최첨단
2024년 Google은 BBRv3 연구를 발표. 주요 개선:
- 더 나은 Wi-Fi/셀룰러 성능.
- 낮은 RTT 변동성.
- 더 빠른 수렴.
Linux 커널에 단계적으로 통합 중이다.
9. Linux에서 사용해 보기
현재 설정 확인
# 사용 가능한 알고리즘
sysctl net.ipv4.tcp_available_congestion_control
# net.ipv4.tcp_available_congestion_control = reno cubic bbr
# 현재 기본값
sysctl net.ipv4.tcp_congestion_control
# net.ipv4.tcp_congestion_control = cubic
BBR 활성화
# 모듈 로드
sudo modprobe tcp_bbr
# 기본값으로 설정
sudo sysctl -w net.ipv4.tcp_congestion_control=bbr
# FQ scheduler도 함께 설정 (pacing용)
sudo sysctl -w net.core.default_qdisc=fq
# 영구 설정
echo "net.core.default_qdisc=fq" | sudo tee -a /etc/sysctl.conf
echo "net.ipv4.tcp_congestion_control=bbr" | sudo tee -a /etc/sysctl.conf
연결별로 다른 알고리즘 쓰기
// C 소켓 프로그래밍
int sock = socket(AF_INET, SOCK_STREAM, 0);
const char *cc = "bbr";
setsockopt(sock, IPPROTO_TCP, TCP_CONGESTION, cc, strlen(cc));
# Python
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_CONGESTION, b"bbr")
측정해 보기
# iperf3로 벤치마크
# 서버
iperf3 -s
# 클라이언트 (CUBIC)
iperf3 -c server --congestion=cubic -t 30
# 클라이언트 (BBR)
iperf3 -c server --congestion=bbr -t 30
고대역폭 고지연 경로에선 차이가 극명하다.
10. 혼잡 제어 관찰하기
ss 명령어
ss -ti
# ESTAB 0 0 1.2.3.4:80 5.6.7.8:443
# cubic wscale:7,7 rto:204 rtt:4.5/1.5 mss:1448 pmtu:1500
# rcvmss:536 advmss:1448 cwnd:10 bytes_acked:1234
# segs_out:10 segs_in:5 send 25.7Mbps lastsnd:12 ...
cubic: 사용 중인 알고리즘.rtt: 현재 RTT / 표준편차.cwnd: 현재 혼잡 윈도우.send: 계산된 전송 속도.
/proc/net/netstat
grep -E "Tcp.*(Retrans|Lost)" /proc/net/netstat
# TcpRetransSegs: 12345
# TcpExtTCPLostRetransmit: 123
재전송 통계는 네트워크 상태의 중요한 지표.
BPF로 추적
# bcc: TCP 재전송 추적
sudo tcpretrans
# tcplife: 연결 수명 추적
sudo tcplife
# tcptop: 실시간 TCP 처리량
sudo tcptop
BPF는 커널 수준 혼잡 제어 상태를 실시간으로 관찰할 수 있게 한다.
11. 실전 시나리오와 선택 가이드
시나리오 1: CDN 서버
상황: 전 세계 사용자에게 동영상/이미지 서빙.
추천: BBR 또는 BBRv2.
이유: 다양한 네트워크 조건(모바일, Wi-Fi, 광섬유). 손실률 높을 수 있음. BBR이 평균적으로 뛰어남.
시나리오 2: 데이터센터 내부
상황: Kafka, gRPC 등 내부 통신.
추천: DCTCP (가능하면) 또는 BBR.
이유: 낮은 RTT, 높은 대역폭. DCTCP의 정밀한 큐 제어가 유리. 없으면 BBR.
시나리오 3: 일반 웹 서버
상황: HTTP/HTTPS 트래픽.
추천: BBR 또는 CUBIC (배포 환경에 따라).
이유: CUBIC은 검증된 안전. BBR은 성능 우위. 트래픽 특성에 따라 선택.
시나리오 4: 모바일 앱 백엔드
상황: 4G/5G 네트워크의 클라이언트.
추천: BBR 강력 추천.
이유: 모바일 네트워크는 혼잡이 아닌 이유로 손실 발생. BBR은 이를 덜 과민하게 반응.
시나리오 5: 게이밍 서버
상황: 낮은 지연이 생명.
추천: BBR + FQ_CoDel AQM.
이유: bufferbloat 최소화. 큐가 쌓이지 않아 지연 일관성.
12. 미래: 알고리즘의 미래
Machine Learning 기반
구글, MIT 등에서 ML 기반 혼잡 제어 연구 중:
- Remy: 자동으로 최적 알고리즘 학습.
- PCC-Vivace: 온라인 학습.
- Aurora: Deep RL 기반.
아직 실전 배치는 제한적이지만 미래 방향이다.
QUIC과의 조합
QUIC (HTTP/3의 기반)은 애플리케이션 레벨에서 혼잡 제어를 구현한다. Cloudflare, Google, Meta 모두 QUIC에 BBRv2 변종을 적용했다.
장점: 커널 업데이트 없이 애플리케이션에서 제어. 훨씬 빠른 혁신 주기.
eBPF 기반 제어
Linux 커널에 eBPF 기반 혼잡 제어가 통합되고 있다. 커스텀 알고리즘을 eBPF 프로그램으로 작성해서 커널에 주입 가능.
SEC("struct_ops/cong_control")
int mycong_cong_control(...) {
// 커스텀 로직
}
Meta 등이 프로덕션에서 실험 중.
퀴즈로 복습하기
Q1. 왜 TCP Reno는 고대역폭 고지연 환경에서 비효율적인가?
A. Reno는 매 RTT마다 cwnd를 1 MSS(~1500바이트)씩 증가시킨다. 10 Gbit/s 링크에 100ms RTT면 BDP는 125 MB다. Reno가 이 수준의 cwnd에 도달하려면 수만 RTT가 걸린다 (125,000,000 / 1500 ≈ 83,000번). 실제로는 그 전에 패킷 손실이 발생해 cwnd가 절반으로 줄고, 다시 처음부터 올라간다. 결과적으로 평균 cwnd가 BDP의 작은 부분만 차지하며 링크 이용률이 매우 낮다. 이 문제를 해결하려고 나온 것이 CUBIC(시간 기반 성장)과 BBR(대역폭 직접 측정)이다.
Q2. BBR이 "손실을 무시한다"는 말의 정확한 의미와 그 이유는?
A. BBR은 패킷 손실을 혼잡의 주 신호로 사용하지 않는다. 대신 실제 측정된 delivery rate와 RTT를 기반으로 BDP를 추정하고, 그에 맞춰 전송한다. 이유:
- Wi-Fi/모바일 네트워크: 패킷 손실이 혼잡이 아닌 비트 오류로 발생할 수 있음.
- AQM 라우터: 혼잡 전에 선제적으로 드롭할 수 있음.
- 대규모 버퍼 링크: 손실이 발생할 때쯤엔 이미 지연이 심각.
BBR은 손실을 완전히 무시하는 건 아니지만, 손실 시 즉시 cwnd를 절반으로 줄이지 않는다. 이것이 BBR의 강점(손실 견딤)이자 약점(CUBIC과 공정성 문제)이다. BBRv2는 이 균형을 조정해서 손실에 적절히 반응한다.
Q3. Bufferbloat는 어떻게 발생하며 BBR이 이를 어떻게 해결하는가?
A. 원인: 현대 라우터가 패킷 손실을 줄이려고 큰 버퍼(수십 MB)를 가진다. 손실 기반 TCP(Reno/CUBIC)는 큐가 가득 찰 때까지 cwnd를 늘리므로, 결국 버퍼가 쌓여 RTT가 수 초까지 치솟는다. 인터랙티브 트래픽(VoIP, 게임)이 이 큰 큐 뒤에서 기다리면서 지연이 심각해진다.
BBR의 해결: BBR은 **측정된 최소 RTT(RTprop)**를 추적한다. 이는 "큐가 비었을 때의 RTT"에 해당한다. BBR은 cwnd를 BtlBw × RTprop 근처에서 유지하므로 큐가 거의 쌓이지 않는다. 주기적(8초마다)으로 PROBE_RTT 상태에서 cwnd를 크게 줄여 RTprop을 재측정한다. 결과: 큐가 비어있어 RTT가 낮게 유지되고, 인터랙티브 트래픽의 지연이 극적으로 개선된다.
Q4. BBR과 CUBIC이 공존할 때 왜 불공정할 수 있는가?
A. 두 알고리즘이 "혼잡 신호"를 해석하는 방식이 완전히 다르기 때문이다:
- CUBIC: 패킷 손실을 보면 즉시 cwnd를 절반으로 줄임. 매우 "예의 바른" 동작.
- BBR: 손실을 대부분 무시. 자신의 pacing rate를 유지.
같은 병목에서 공존하면:
- BBR이 큐를 채울 정도로 보냄.
- 큐가 가득 차면 CUBIC이 먼저 손실을 겪고 양보.
- BBR은 그 빈 자리를 "내가 보낼 수 있는 더 많은 대역폭"으로 해석.
- CUBIC의 할당량이 점점 줄어듬.
특정 조건(대규모 버퍼, 높은 RTT)에선 BBR이 CUBIC의 4배 이상 대역폭을 차지할 수 있다. 이는 심각한 공정성 문제로, BBRv2는 이를 해결하기 위해 손실과 ECN에 적절히 반응하도록 개선되었다.
Q5. Linux에서 BBR을 활성화할 때 왜 FQ scheduler도 함께 설정해야 하는가?
A. BBR은 pacing rate 기반으로 동작한다. "지금부터 1초 동안 100 MB 전송"이 아니라, "각 패킷을 정확히 Xms 간격으로 전송"하는 방식이다. 이 정밀한 타이밍 제어는 큐 스케줄러(qdisc) 의 지원이 필요하다.
Linux의 기본 qdisc인 pfifo_fast는 pacing을 지원하지 않는다. BBR이 "천천히 보내줘"라고 요청해도 qdisc가 즉시 다 내보낸다. 결과: BBR의 핵심 이점인 큐잉 지연 최소화가 무효화된다.
FQ (Fair Queue) qdisc는:
- 패킷 pacing 지원 — BBR의 요청대로 간격 조절.
- 흐름별 공정성 — 동시 다수 흐름 간 균등 분배.
- BBR과의 긴밀한 통합 — Linux 커널에서 공식 권장.
sysctl -w net.core.default_qdisc=fq 없이 BBR만 켜면 성능이 기대만큼 나오지 않는다. 실제로 Google의 BBR 논문도 FQ 사용을 명시한다.
마치며: 인터넷의 숨은 알고리즘
핵심 정리
- TCP Reno: 1988년, AIMD로 혼잡 붕괴 해결. 인터넷의 구원자.
- CUBIC: 고대역폭 고지연을 위한 3차 함수 성장. 2010년대 표준.
- BBR: 손실이 아닌 대역폭과 RTT 직접 측정. Google의 혁명.
- BBRv2: 공정성 복원. Google의 현재.
- Bufferbloat: 큰 버퍼가 만든 지연 재앙. BBR의 존재 이유.
- DCTCP: 데이터센터 특화, ECN 기반.
왜 이 지식이 중요한가?
혼잡 제어는 보이지 않는 알고리즘이다. 매일 사용하는 모든 TCP 연결이 이 알고리즘 위에서 돌아가지만, 대부분의 개발자는 관심을 두지 않는다. 그러나:
- 성능 문제 디버깅: "왜 업로드가 느리지?"의 답이 혼잡 제어에 있을 수 있다.
- CDN 설계: 어떤 알고리즘을 쓸지가 사용자 경험을 좌우.
- 모바일 앱 최적화: 손실이 많은 환경에서 BBR의 효과.
- 운영 튜닝: sysctl 하나로 성능이 바뀔 수 있다.
마지막 교훈
Van Jacobson이 1988년에 만든 AIMD가 40년 후에도 여전히 인터넷의 대부분을 움직인다. 그 위에 CUBIC, BBR이 쌓였고, 미래엔 ML 기반이나 QUIC 내장 알고리즘이 올 것이다. 그러나 본질은 변하지 않는다:
"보이지 않는 네트워크 용량을 추정하고, 공평하게 나누고, 낭비 없이 쓰기."
이 단순하지만 어려운 문제를 풀기 위해 수십 년간 수많은 엔지니어가 노력해 왔다. 당신이 지금 이 글을 읽는 이 순간에도, 수십 개의 TCP 연결이 각자의 혼잡 제어 상태를 계산하며 데이터를 나르고 있다. 그 사실을 아는 것이 더 좋은 엔지니어가 되는 첫걸음이다.
참고 자료
- Congestion Avoidance and Control (Jacobson, 1988) - TCP 혼잡 제어의 원 논문
- CUBIC: A New TCP-Friendly High-Speed TCP Variant (Ha et al., 2008)
- BBR: Congestion-Based Congestion Control (Cardwell et al., 2016) - Google의 BBR 소개
- Making Linux TCP Fast (Dumazet)
- Bufferbloat: Dark Buffers in the Internet (Gettys, 2011)
- Linux TCP Implementation
- BBRv2 Overview (Google)
- Understanding BBR (APNIC)
- Cloudflare: HTTP/2 Rapid Reset Attack - 혼잡 제어 관련 운영 경험
- Google BBR at IETF 104
현재 단락 (1/300)
1986년 10월, 초기 인터넷에서 이상한 일이 벌어졌다. UC 버클리와 로렌스 버클리 연구소 사이의 처리량이 갑자기 **32 kbit/s에서 40 bit/s로 떨어졌다**. **...