HTTP/3(QUIC)
HTTP/1.1과 HTTP/2는 TCP 위에서 동작합니다. TCP는 데이터를 순서대로, 빠짐없이 전달해 주는 믿을 만한 프로토콜이지만, 이 성질이 웹 성능에는 걸림돌이 되기도 합니다. 연결을 맺는 데 왕복이 여러 번 필요하고, 패킷 하나가 유실되면 그 뒤의 데이터가 모두 기다려야 하며, 네트워크가 바뀌면 연결이 끊어집니다. HTTP/3는 이런 문제를 줄이려고 TCP 대신 UDP 위에서 동작하는 QUIC을 전송 프로토콜로 씁니다.
QUIC은 UDP를 바탕으로 신뢰성 있는 전달, 혼잡 제어, 암호화(TLS 1.3), 여러 스트림의 다중화를 직접 제공하는 전송 프로토콜이며, 대부분의 구현이 운영체제 커널이 아니라 사용자 공간 라이브러리로 동작합니다. HTTP/3는 이 QUIC 위에서 HTTP의 의미(메서드, 헤더, 상태 코드)를 그대로 쓰는 새 매핑입니다. 이 토픽에서는 연결 수립에 걸리는 시간, 패킷 유실이 스트림에 미치는 영향, 네트워크가 바뀔 때의 연결 유지를 단계별로 봅니다.
핵심 개념
QUIC
UDP 위에서 동작하는 전송 프로토콜입니다. 재전송과 혼잡 제어로 신뢰성을 직접 보장하고, TLS 1.3 암호화를 기본으로 통합했습니다. 표준은 RFC 9000입니다.
핵심 개념
HTTP/3
QUIC 위에서 동작하는 HTTP 버전입니다. 요청과 응답의 의미는 HTTP/2와 같고, 전송 방식과 헤더 압축(QPACK)이 달라졌습니다. 표준은 RFC 9114입니다.
핵심 개념
HOL 블로킹
앞의 데이터가 늦어져 뒤의 데이터가 함께 기다리는 현상입니다. TCP 한 연결에 스트림을 여럿 태운 HTTP/2에서는 패킷 유실이 모든 스트림을 지연시킬 수 있습니다.
핵심 개념
스트림 다중화
QUIC은 한 연결 안에 여러 스트림을 두고, 스트림마다 독립적으로 순서를 관리합니다. 한 스트림의 유실이 다른 스트림의 전달을 막지 않습니다.
핵심 개념
0-RTT와 1-RTT
QUIC은 전송과 TLS 1.3 핸드셰이크를 합쳐 처음 연결을 1 RTT에 맺습니다. 전에 접속한 서버에는 첫 패킷에 데이터를 실어 보내는 0-RTT 재연결도 가능합니다.
핵심 개념
연결 ID와 연결 이동
QUIC은 연결을 IP 주소와 포트가 아니라 연결 ID로 식별합니다. 그래서 Wi-Fi에서 모바일 네트워크로 옮겨 가 주소가 바뀌어도 연결을 이어 갈 수 있습니다.
HTTP/3의 배경
QUIC은 구글이 실험하던 프로토콜에서 출발해 IETF에서 표준화되었고, 2021년 5월에 RFC 9000(QUIC 전송), RFC 9001(TLS 통합), RFC 9002(유실 감지와 혼잡 제어)로 발표되었습니다. HTTP/3는 2022년 6월 RFC 9114로 표준이 되었습니다. TCP는 운영체제 커널과 중간 장비에 깊이 박혀 있어 고치기 어렵지만, QUIC은 UDP 위에서 대부분 사용자 공간 라이브러리로 구현되어 애플리케이션 업데이트만으로 개선할 수 있다는 점도 도입 이유의 하나입니다.
숫자로 확인하기
자주 하는 오해
면접에서 이렇게 물어요
면접 질문
HTTP/3는 HTTP/2와 무엇이 다른가요?
전송 계층이 TCP에서 UDP 위의 QUIC으로 바뀌었습니다. 그 결과 연결 수립이 빨라지고, 패킷 유실이 다른 스트림을 막는 HOL 블로킹이 줄며, 네트워크가 바뀌어도 연결을 유지할 수 있습니다.
면접 질문
HTTP/2의 HOL 블로킹이 왜 생기나요?
HTTP/2는 TCP 연결 하나에 여러 스트림을 태우는데, TCP는 연결 전체의 바이트 순서를 보장합니다. 패킷 하나가 유실되면 재전송될 때까지 뒤의 모든 스트림 데이터가 전달되지 못합니다.
면접 질문
QUIC은 UDP를 쓰는데 어떻게 신뢰성을 보장하나요?
QUIC 자체가 패킷 번호, 확인 응답, 재전송, 혼잡 제어를 구현합니다. 신뢰성을 UDP가 아니라 QUIC이 맡는 구조입니다.
면접 질문
브라우저는 서버가 HTTP/3를 지원하는지 어떻게 아나요?
보통 처음에는 TCP로 접속하고, 서버가 응답에 담아 보낸 Alt-Svc 헤더(예: h3=":443")를 보고 이후 요청부터 HTTP/3를 시도합니다. DNS의 HTTPS 레코드로 처음부터 알 수도 있습니다. 실패하면 TCP로 돌아갑니다.
실제 시스템에서는
함께 보면 좋은 토픽
참고 자료
nginx, curl — HTTP/3 켜고 확인하기
# nginx.conf (HTTP/3를 지원하게 빌드된 nginx 1.25 이상) server { listen 443 quic reuseport; # UDP 443: HTTP/3 listen 443 ssl; # TCP 443: HTTP/2, HTTP/1.1 (대비용) http2 on; # nginx 1.25.1 이상 http3 on; ssl_certificate /etc/ssl/example.crt; ssl_certificate_key /etc/ssl/example.key; add_header Alt-Svc 'h3=":443"; ma=86400'; } # 확인: Alt-Svc 헤더와 HTTP/3 응답 curl -sI https://www.example.com/ | grep -i alt-svc curl --http3-only -I https://www.example.com/
HTTP/3는 QUIC 위에서 UDP로 동작하므로 nginx는 listen 443 quic으로 UDP 443 포트를 따로 열고, http3 on으로 HTTP/3를 켭니다. 같은 포트의 TCP에서는 listen 443 ssl과 http2 on(nginx 1.25.1 이상)으로 HTTP/2와 HTTP/1.1을 계속 처리합니다. 브라우저는 처음에 TCP로 접속해 응답의 Alt-Svc 헤더에서 이 서버가 h3를 지원한다는 것을 알고, 이후 요청부터 HTTP/3를 시도합니다. ma는 이 정보를 기억하는 시간(초)이며, UDP가 막힌 네트워크에서는 TCP로 대신 접속합니다. curl의 --http3-only 옵션은 HTTP/3를 지원하게 빌드된 curl에서만 동작하며, HTTP/3가 안 되면 다른 버전으로 대체하지 않고 실패합니다(--http3는 대체합니다). 방화벽에서 UDP 443이 열려 있어야 HTTP/3가 쓰입니다.
Python — 첫 응답까지 걸리는 시간 계산
RTT = 50 # 왕복 시간 (ms) def first_response(handshake_rtts): # 핸드셰이크 왕복 수 + 요청 · 응답 1왕복 return (handshake_rtts + 1) * RTT print(first_response(2)) # TCP 1 + TLS 1.3 1 -> 150 print(first_response(1)) # QUIC (TLS 1.3 통합) -> 100 print(first_response(0)) # QUIC 0-RTT 재연결 -> 50
첫 응답이 도착하는 시간은 핸드셰이크에 쓰는 왕복 수에 요청과 응답의 1왕복을 더한 값에 RTT를 곱한 것입니다. 서버 처리 시간은 뺀 단순한 계산입니다. TCP는 연결 수립에 1 RTT, TLS 1.3 핸드셰이크에 1 RTT가 필요해 2를 넣고, QUIC은 전송과 TLS 1.3 핸드셰이크를 합쳐 1 RTT이므로 1을 넣습니다. 전에 접속한 서버와의 0-RTT 재연결은 첫 패킷에 요청을 실어 보내므로 0입니다. 이 값은 시각화의 연결 수립 탭이 보여 주는 150ms, 100ms, 50ms와 같으며, RTT가 클수록 차이도 그만큼 커집니다.
Python — 패킷 유실 때의 전달 가능 패킷 비교
WIRE = [(s, n) for n in range(1, 4) for s in "ABC"] # A1 B1 C1 A2 ... received = set(WIRE) - {("A", 2)} # A2가 유실됨 def deliver_tcp(received): out = [] for pkt in WIRE: # 연결 전체의 순서를 지킴 if pkt not in received: break out.append(pkt) return out def deliver_quic(received): out = [] for s in "ABC": # 스트림마다 순서를 지킴 n = 1 while (s, n) in received: out.append((s, n)) n += 1 return out print(len(deliver_tcp(received)), len(deliver_quic(received))) # 3 7
WIRE는 스트림 A, B, C의 패킷이 번갈아 도착하는 순서이고, 그 가운데 A2가 유실된 상황을 만듭니다. deliver_tcp는 연결 전체의 순서를 지키므로 첫 빈자리(A2)를 만나면 멈추고, 그 앞의 A1, B1, C1 세 개만 애플리케이션에 전달합니다. deliver_quic은 스트림마다 순서를 따로 지키므로 스트림 A는 A1에서 멈추지만 스트림 B와 C는 끝까지 전달해 모두 7개를 전달합니다. 이 차이가 TCP 위의 HTTP/2에서 생기는 HOL 블로킹과 HTTP/3에서 줄어드는 이유이며, 시각화의 패킷 유실 탭과 같은 결과입니다. 실제 QUIC은 유실된 패킷을 재전송해 결국 모든 데이터를 전달합니다.
Q1
HTTP/3이 사용하는 전송 프로토콜은?
Q2
TCP 기반 HTTP/2에서 패킷 하나가 유실되면 어떤 문제가 생길까?
Q3
QUIC의 연결 수립 관련 장점은?