HOL 블로킹이 왜 문제인가
HTTP/2는 하나의 TCP 연결에서 여러 스트림을 다중화(multiplexing)한다. 애플리케이션 계층에서는 스트림이 독립적이지만, 그 아래 TCP는 바이트 스트림을 순서대로 전달한다. 중간에 패킷 하나가 유실되면 TCP는 재전송이 도착할 때까지 뒤따르는 모든 세그먼트를 상위 계층에 넘기지 못한다. 결과적으로 손실과 무관한 다른 스트림까지 전부 멈춘다. 이것이 전송 계층 HOL(Head-of-Line) 블로킹이며, 손실률이 높은 모바일·무선 환경일수록 체감 지연이 커진다.
QUIC이 해결하는 방식
QUIC은 UDP 위에서 동작하며 스트림 다중화를 전송 계층 내부로 끌어올렸다. 각 스트림은 독립적인 시퀀스 공간을 가지므로, 한 스트림의 패킷이 유실돼도 다른 스트림의 데이터는 즉시 애플리케이션에 전달된다. TCP+TLS의 3-way + TLS 핸드셰이크가 QUIC에서는 1-RTT로 통합되고, 이전에 연결했던 서버라면 0-RTT 재개도 가능하다. 또한 Connection ID로 연결을 식별하므로 IP가 바뀌어도(Wi-Fi↔LTE) 연결이 유지되는 커넥션 마이그레이션을 지원한다.
| 항목 | HTTP/2 (TCP) | HTTP/3 (QUIC) |
|---|---|---|
| 전송 | TCP | UDP |
| 스트림 독립성 | 애플리케이션만 | 전송 계층까지 |
| 손실 시 영향 | 전 스트림 정지 | 해당 스트림만 |
| 핸드셰이크 | TCP+TLS 다중 RTT | 1-RTT / 0-RTT |
| 커넥션 마이그레이션 | 불가 | 가능 |
Nginx에서 HTTP/3 켜기
Nginx는 1.25.0부터 HTTP/3를 정식 지원한다. TLS 1.3이 필수이며, TCP 443과 UDP 443을 함께 열어야 한다. Alt-Svc 헤더로 브라우저에 HTTP/3 엔드포인트를 알린다.
server {
# 기존 HTTP/2 (TCP) 유지 — 폴백 경로
listen 443 ssl;
http2 on;
# HTTP/3 (QUIC over UDP)
listen 443 quic reuseport;
ssl_protocols TLSv1.3;
ssl_certificate /etc/ssl/site.crt;
ssl_certificate_key /etc/ssl/site.key;
# 브라우저에 h3 가용성 광고 (24시간)
add_header Alt-Svc 'h3=":443"; ma=86400' always;
}
방화벽과 커널 설정
가장 흔한 실수는 UDP 443을 열지 않는 것이다. TCP만 열려 있으면 조용히 HTTP/2로 폴백되어 "적용된 줄 알았는데 아님" 상태가 된다. QUIC은 유저스페이스에서 혼잡 제어를 처리하므로 UDP 송수신 버퍼도 넉넉히 잡는다.
# UDP 443 개방
sudo ufw allow 443/udp
# UDP 버퍼 확대 (QUIC 처리량 개선)
sudo sysctl -w net.core.rmem_max=8388608
sudo sysctl -w net.core.wmem_max=8388608
# 실제 h3 협상 확인 (curl은 --http3-only 지원 빌드 필요)
curl -sI --http3-only https://example.com | grep -i 'HTTP/3\|alt-svc'
적용 여부 검증
배포 후에는 헤더만 보지 말고 실제 프로토콜 협상을 확인해야 한다. 아래처럼 응답의 협상 결과를 로깅하면 폴백 비율을 모니터링할 수 있다.
import httpx # HTTP/3 지원 빌드 기준
async def probe(url: str) -> str:
async with httpx.AsyncClient(http2=True) as client:
r = await client.get(url)
# 실제 협상된 프로토콜 확인
return r.http_version # "HTTP/2" 또는 "HTTP/3"
주의점과 트레이드오프
QUIC이 만능은 아니다. 유저스페이스 처리 특성상 커널 TCP보다 CPU 사용량이 높을 수 있으므로, 대량 트래픽에서는 GSO/GRO 오프로딩 지원 여부를 확인한다. 또 일부 기업망·미들박스는 UDP 443을 차단하거나 속도 제한을 걸어 QUIC이 무력화될 수 있으니, HTTP/2 폴백을 반드시 유지해야 한다. 0-RTT는 재전송 공격에 취약하므로 멱등하지 않은 요청(결제·주문 생성 등)에는 적용하지 않는다.
정리
HOL 블로킹은 TCP의 순서 보장이라는 특성에서 비롯되며, QUIC은 이를 전송 계층 스트림 독립성으로 근본 해결한다. 도입은 UDP 443 개방, TLS 1.3, Alt-Svc 광고, 그리고 HTTP/2 폴백 유지가 핵심이다. 손실이 잦은 모바일 트래픽 비중이 큰 서비스일수록 효과가 뚜렷하지만, CPU 비용과 미들박스 호환성을 함께 계측한 뒤 단계적으로 전환하는 것이 안전하다.