HTTP/3와 QUIC — UDP 위에서 다시 짠 전송
HTTP/3가 왜 TCP를 버리고 UDP 기반 QUIC을 택했는지, TCP HOL 블로킹을 전송 계층에서 푸는 방식과 0-RTT 핸드셰이크, 연결 마이그레이션, QPACK 헤더 압축, Alt-Svc 협상, 그리고 UDP 미들박스 이슈까지 정리합니다.
지난 글에서 HPACK으로 헤더를 압축하고 서버 푸시가 Early Hints에 자리를 내준 이야기까지, HTTP/2가 응용 계층에서 할 수 있는 최적화는 거의 다 짚었다. 그런데 HTTP/2에는 응용 계층 노력만으로는 절대 못 푸는 벽이 하나 남아 있었다. 바로 그 밑에 깔린 TCP 자체다. HTTP/3는 이 벽을 넘기 위해 30년 넘게 웹을 떠받쳐 온 TCP를 버리고, UDP 위에 QUIC이라는 새 전송 프로토콜을 얹는 과감한 선택을 했다. 이번 글은 그 결정의 배경과 QUIC이 무엇을 다시 짰는지를 다룬다.
HTTP/2가 못 넘은 벽 — TCP HOL 블로킹
HTTP/2는 하나의 TCP 연결 안에서 여러 스트림을 다중화한다. 응용 계층에서 보면 스트림들은 서로 독립적이다. 그런데 그 모든 스트림이 하나의 TCP 바이트 스트림 위를 흐른다는 게 문제였다.
TCP는 “보낸 순서대로, 빠짐없이” 데이터를 전달하는 것을 보장한다. 그래서 중간 패킷 하나가 유실되면, 그 뒤에 무사히 도착한 패킷들도 잃어버린 패킷이 재전송될 때까지 커널 버퍼에 갇혀 응용 계층으로 올라가지 못한다. 스트림 3번의 패킷 하나가 빠지면, 멀쩡히 도착한 스트림 5번·7번의 데이터까지 줄줄이 멈춘다. 이것이 TCP 레벨의 HOL(Head-of-Line) 블로킹이다.
HTTP/1.1의 HOL은 요청 단위였고 HTTP/2가 다중화로 그걸 풀었지만, 정작 그 다중화가 단일 TCP 연결에 묶이면서 HOL이 전송 계층으로 내려가 버린 셈이다. 응용 계층에서 스트림을 아무리 나눠도, TCP가 한 줄로 순서를 강제하는 한 이 문제는 풀 수 없다.
해법은 전송 계층을 갈아치우는 것
QUIC의 핵심은 스트림 다중화를 전송 계층 안으로 끌어내린 것이다. QUIC은 각 스트림을 독립적으로 관리해, 한 스트림에서 패킷이 유실돼도 다른 스트림은 영향받지 않고 계속 응용 계층으로 전달된다. 스트림 3번이 재전송을 기다리는 동안에도 스트림 5번·7번은 그대로 흘러간다. TCP HOL 블로킹이 전송 계층 설계로 사라지는 것이다.
그런데 TCP는 운영체제 커널에 깊이 박혀 있고 전 세계 미들박스가 TCP 동작을 전제로 굳어 있어서, TCP 자체를 고치는 것은 현실적으로 불가능했다. 그래서 QUIC은 이미 모든 곳을 통과하는 UDP 위에 신뢰성·순서·혼잡 제어·암호화를 전부 새로 구현했다. UDP는 “포트 번호만 붙인 IP”에 가까운 얇은 프로토콜이라, 그 위에 원하는 전송 시맨틱을 자유롭게 올릴 수 있다.
프로토콜 스택 — HTTP/2 vs HTTP/3
스택을 나란히 놓으면 차이가 분명하다.
HTTP/2 HTTP/3
───────────── ─────────────
HTTP/2 HTTP/3
TLS 1.2/1.3 QUIC ← TLS 1.3 + 전송을 통합
TCP UDP
IP IP
HTTP/2에서는 TCP가 연결을 맺고, 그 위에서 TLS가 또 한 번 핸드셰이크를 하고, 그 위에서 HTTP가 동작하는 3단 계층이었다. QUIC은 신뢰성 전송과 TLS 1.3 암호화를 한 덩어리로 통합했다. 암호화가 선택이 아니라 프로토콜의 일부라서, QUIC에 평문 모드는 없다. 헤더부터 페이로드까지 사실상 전부 암호화된다.
QUIC이 다시 짠 것들
1. 0-RTT / 1-RTT 핸드셰이크
기존 HTTPS는 TCP 3-way 핸드셰이크(1 RTT) 뒤에 TLS 핸드셰이크(12 RTT)가 따로 붙어, 첫 데이터를 보내기까지 23번의 왕복이 필요했다. QUIC은 전송과 TLS 1.3을 합쳐 첫 연결을 1-RTT에 끝낸다. 한 번 만난 적 있는 서버라면 이전 세션 정보를 재사용해 0-RTT, 즉 핸드셰이크 왕복 없이 첫 요청 데이터를 곧장 실어 보낼 수도 있다.
# curl로 HTTP/3 강제 요청 (HTTP/3 지원 빌드 필요)
curl --http3 -sI https://cloudflare.com/ | head -1
# HTTP/3 200
0-RTT 데이터는 재전송(replay) 공격에 노출될 수 있어, 멱등하지 않은 요청(예: 결제)에는 쓰지 않는 등 보안상 제약이 따른다.
2. 연결 마이그레이션 (Connection ID)
TCP 연결은 (출발지 IP, 출발지 포트, 목적지 IP, 목적지 포트) 4-튜플로 식별된다. 그래서 휴대폰이 Wi-Fi에서 LTE로 바뀌어 IP가 달라지면, TCP 연결은 즉시 끊기고 처음부터 다시 맺어야 한다.
QUIC은 연결을 IP·포트가 아니라 Connection ID라는 독립적인 식별자로 구분한다. 그래서 네트워크가 바뀌어 출발지 IP가 달라져도, 같은 Connection ID로 연결을 그대로 이어 간다. 이동 중에도 영상이 끊기지 않는, QUIC의 가장 빛나는 기능이다.
3. QPACK 헤더 압축
HTTP/2의 HPACK은 순서대로 도착하는 TCP를 전제로 동적 테이블을 갱신했다. 그런데 QUIC은 스트림이 독립적으로, 순서 없이 도착할 수 있어 HPACK을 그대로 쓰면 동적 테이블 상태가 어긋난다. 그래서 HPACK을 QUIC에 맞게 다시 설계한 것이 QPACK이다.
QPACK은 정적·동적 테이블과 허프만 인코딩이라는 HPACK의 골격은 그대로 두되, 헤더 압축에 쓰이는 테이블 갱신 정보를 별도의 인코더/디코더 스트림으로 분리한다. 이렇게 하면 한 요청 스트림의 지연이 다른 스트림의 헤더 해석을 막는 HOL 블로킹이 헤더 압축 단에서도 생기지 않는다.
4. Alt-Svc로 업그레이드 협상
브라우저가 처음 사이트에 접속할 때는 보통 TCP/HTTPS로 붙는다. 그러면 서버가 응답에 Alt-Svc(Alternative Services) 헤더를 실어 “나는 HTTP/3도 지원한다”고 알린다.
HTTP/1.1 200 OK
Alt-Svc: h3=":443"; ma=86400
h3가 HTTP/3, ma(max-age)가 이 정보의 유효 기간이다. 브라우저는 이 힌트를 기억해 두고, 다음 접속부터 QUIC(UDP/443)으로 전환을 시도한다. 즉 HTTP/3는 기존 HTTPS 위에서 점진적으로 업그레이드되는 구조다.
UDP라서 생기는 현실 문제
QUIC이 UDP를 고른 것은 영리했지만 공짜는 아니었다.
- 방화벽·미들박스 차단: 상당수 기업 방화벽이 UDP/443을 막아 둔다. 웹 트래픽은 TCP라는 통념 때문이다. 그래서 브라우저는 QUIC이 막혔다고 판단되면 TCP 기반 HTTP/2로 폴백한다. HTTP/3는 HTTP/2를 대체한다기보다 가능한 환경에서 우선 시도하는 관계다.
- 미들박스의 간섭 불가: QUIC은 헤더 대부분을 암호화해 중간 장비가 패킷 내용을 들여다보거나 조작하지 못한다. 보안에는 좋지만, TCP 동작을 가정하고 최적화하던 캐리어 장비들이 QUIC을 제대로 다루지 못하는 과도기 문제가 있었다.
- CPU 비용: 신뢰성·혼잡 제어가 커널의 TCP가 아니라 사용자 공간 라이브러리에서 돌아가는 경우가 많아, 같은 트래픽에서 CPU를 더 쓰는 경향이 있었다. 커널 UDP 오프로딩(GSO 등)으로 점차 개선되는 중이다.
배포 현황과 정리
HTTP/3는 2022년 RFC 9114로 표준화됐고, 이미 웹의 큰 축을 차지한다. Google·Cloudflare·Meta·Akamai 등 주요 CDN과 서비스가 HTTP/3를 켜 두고 있고, 크롬·파이어폭스·사파리 등 주요 브라우저가 기본 지원한다. 통계 출처마다 다르지만 상위 사이트 상당수가 HTTP/3를 광고(Alt-Svc)하고 있다.
HTTP/2 : 다중화는 했지만 단일 TCP라 전송 계층 HOL 블로킹 잔존
HTTP/3 : QUIC(UDP 기반)으로 스트림별 독립 전달 → 전송 HOL 해소
+ 0/1-RTT 핸드셰이크, 연결 마이그레이션, QPACK, 내장 암호화
- HTTP/3가 TCP를 버린 본질적 이유는 TCP HOL 블로킹이다. 응용 계층 다중화로는 풀 수 없어, 전송 계층 자체를 QUIC으로 다시 짰다.
- QUIC은 UDP 위에 신뢰성·순서·혼잡 제어·TLS 1.3 암호화를 통합했다. 0-RTT 핸드셰이크, 연결 마이그레이션, QPACK 같은 기능이 그 위에서 나온다.
- 대가는 UDP에서 오는 방화벽·미들박스·CPU 이슈이며, 막히면 HTTP/2로 폴백하고
Alt-Svc로 점진 업그레이드하는 구조로 현실과 타협했다.
HTTP/1.1부터 HTTP/3까지, 웹 프로토콜이 무엇을 풀려고 어떻게 진화했는지를 여기까지 따라와 봤다.
지난 글: HPACK 헤더 압축과 서버 푸시
읽어주셔서 감사합니다. 😊