대부분의 개발자가 신경 쓰지 않는 전송 계층을 통해 API가 제공하는 모든 HTTP 요청이 처리됩니다. 25년 동안 그 답은 TCP였습니다. 그러다 Google은 TCP가 개선되기를 기다리는 것에 지쳐 UDP 위에 QUIC 프로토콜을 구축했고, IETF는 이를 표준으로 만들었습니다. HTTP/3는 QUIC 위에서 실행되도록 구축된 HTTP 버전입니다.
배관 공사처럼 들립니다. 대부분 그렇습니다. 하지만 이 배관은 API가 연결되는 속도, 불안정한 모바일 네트워크에서의 동작 방식, 병렬 요청이 연결을 공유하는 방식에 변화를 가져옵니다. API를 설계하거나 운영한다면, 무엇이 바뀌었고 무엇이 바뀌지 않았는지, 그리고 오늘날 자신의 엔드포인트가 어떤 프로토콜을 사용하는지 확인하는 방법을 알아야 합니다.
이 모든 것에서 한 가지는 변함없이 유지됩니다. HTTP/1.1, HTTP/2, HTTP/3에서 요청, 응답, 상태 코드, JSON 페이로드는 동일하게 보입니다. Apidog와 같은 도구는 API 계층에서 테스트 및 디버깅하므로, 인프라가 어떤 전송 버전을 협상하든 엔드포인트의 동작에 대해 유효성을 검사하는 모든 것이 그대로 적용됩니다. HTTP/2가 무엇이며 HTTP/2 API를 테스트하는 방법에 대한 이전 글을 이미 읽으셨다면, 이 글은 그 글이 끝나는 지점에서 이어집니다.
QUIC 프로토콜이란 무엇인가요?
QUIC은 RFC 9000에서 표준화된 전송 프로토콜입니다. TCP 대신 UDP 위에서 실행되며, TCP가 제공하는 기능(신뢰성, 순서 지정, 혼잡 제어)을 사용자 공간에서 스트림별로 재구축하며, 첫 패킷부터 암호화가 내장되어 있습니다.
네 가지 설계 결정이 이를 정의합니다.
UDP 위에서 실행됩니다. TCP는 운영체제 커널과 인터넷 전반의 미들박스에 구현되어 있어 진화가 거의 불가능합니다. UDP는 전송 보장이 없는 얇은 봉투이므로, QUIC은 그 위에 자체 신뢰성 계층을 구축하고 OS 업그레이드 대신 라이브러리 업데이트 형태로 개선 사항을 제공할 수 있습니다.
TLS 1.3이 내장되어 있습니다. TCP에서는 TCP 핸드셰이크를 완료한 다음 그 위에 별도의 TLS 핸드셰이크를 수행합니다. QUIC은 이들을 통합합니다. 암호화 설정은 전송 핸드셰이크 자체 내에서 이루어지므로, 단일 왕복 후에 새로운 보안 연결이 준비됩니다. 암호화되지 않은 QUIC이란 존재하지 않습니다.
스트림은 독립적입니다. QUIC 연결은 여러 스트림을 전송하며, 각 스트림은 독립적으로 전달됩니다. 손실된 패킷은 해당 스트림만 지연시킵니다. 이는 잠시 후에 설명할 TCP의 Head-of-Line Blocking 문제를 해결합니다.
네트워크 변경에도 연결이 유지됩니다. TCP는 IP 주소와 포트로 연결을 식별합니다. 둘 중 하나라도 변경되면(Wi-Fi 범위를 벗어나거나 5G로 전환) 연결이 끊어집니다. QUIC은 대신 연결 ID로 연결을 식별하므로, 클라이언트가 새 네트워크로 이동해도 동일한 논리적 연결을 유지할 수 있습니다. 재연결이나 새로운 핸드셰이크가 필요 없습니다.
RFC 9114에 정의된 HTTP/3는 HTTP 의미론을 QUIC 스트림에 매핑한 것입니다. 동일한 메서드, 동일한 헤더, 동일한 상태 코드를 사용합니다. 와이어 포맷과 전송 방식만 다릅니다.
HTTP/3 vs HTTP/2: 실제 어떤 점이 달라졌는가
HTTP/2는 HTTP/1.1에 비해 큰 발전이었습니다. 멀티플렉싱을 도입하여 대기열을 만들거나 6개의 병렬 소켓을 여는 대신 여러 요청이 하나의 TCP 연결을 공유할 수 있게 했습니다. 하지만 여전히 TCP를 기반으로 했고, 이는 HTTP/2가 자체적으로 해결할 수 없는 문제를 야기했습니다.
TCP는 단일 바이트 스트림의 순서 있는 전달을 보장합니다. 패킷 하나가 손실되면, TCP는 재전송이 도착할 때까지 그 이후의 모든 바이트를 붙잡아 둡니다. 심지어 완전히 관련 없는 HTTP/2 스트림에 속하는 바이트까지도 말입니다. 하나의 손실된 패킷이 연결에서 멀티플렉싱된 20개의 모든 요청을 멈추게 합니다. 이것이 전송 계층의 Head-of-Line Blocking이며, 손실이 많은 네트워크에서는 여러 연결을 사용하는 HTTP/1.1보다 HTTP/2를 더 느리게 만들 수 있습니다.
HTTP/3는 공유 바이트 스트림을 제거합니다. 각 요청은 자체 전송 순서를 가진 고유한 QUIC 스트림에 매핑됩니다. 스트림 5를 전송하는 패킷이 손실되어도, 스트림 6부터 24까지는 계속 흐릅니다. 멀티플렉싱은 HTTP/2 다이어그램에서 항상 주장했던 방식대로 마침내 작동합니다.
핸드셰이크 방식도 변경되었습니다.
| TCP+TLS 1.3 기반 HTTP/2 | QUIC 기반 HTTP/3 | |
|---|---|---|
| 새 연결 설정 | 2 왕복 (TCP + TLS) | 1 왕복 |
| 재개된 연결 | 1 왕복 | 0 왕복 (0-RTT) |
| 패킷 손실 영향 | 모든 스트림 차단 | 하나의 스트림 차단 |
| 네트워크 전환 (Wi-Fi에서 5G로) | 연결 끊김, 완전 재연결 | 연결 마이그레이션, 계속 유지 |
| 암호화 | 이론상 선택 사항, 별도 계층 | 필수, 통합 TLS 1.3 |
0-RTT 행에는 주의할 점이 있습니다. 클라이언트가 이전에 연결했던 서버에 다시 연결할 때, QUIC은 핸드셰이크가 완료되기 전에 첫 번째 패킷에 애플리케이션 데이터를 보낼 수 있도록 합니다. 이는 지연 시간 단축에 매우 좋습니다. 하지만 0-RTT 데이터는 공격자에 의해 캡처 및 재전송될 수 있으므로, 서버는 0-RTT에서 멱등성(idempotent) 요청만 허용해야 합니다. 재전송된 GET 요청은 무해하지만, 신용카드를 청구하는 재전송된 POST 요청은 그렇지 않습니다. 엣지에서 0-RTT를 활성화하는 경우, 멱등성이 없는 API 호출이 제외되도록 하거나, CDN에서 이 작업을 수행하는지 확인해야 합니다.
HTTP/3가 API에 미치는 영향
프로토콜 업그레이드는 측정 가능한 무언가를 변경할 때만 의미가 있습니다. 여기서는 HTTP/3가 API 트래픽에 어떤 영향을 미치는지, 그리고 어떤 영향이 없는지 설명합니다.
연결 설정 비용 절감
60ms RTT 연결을 사용하는 일반적인 모바일 클라이언트는 첫 번째 API 요청이 장치를 떠나기 전까지 TCP+TLS 설정에 약 120ms를 소비합니다. HTTP/3는 이를 약 60ms로 줄이며, 재개 시에는 거의 0ms에 가깝습니다. (콜드 스타트, 백그라운드 깨우기, 짧은 세션 등) 자주 새 연결을 여는 모바일 앱에서 호출되는 API의 경우, 이러한 절감 효과는 첫 번째 요청마다 나타납니다. 웜(warm) 연결 풀을 유지하는 서버-서버 통합의 경우, 핸드셰이크 비용은 상각되어 무의미해지므로 아무것도 느끼지 못할 것입니다.
모바일 클라이언트의 연결 끊김 감소
연결 마이그레이션은 API 팀에게는 숨겨진 강력한 기능입니다. 사용자가 사무실 Wi-Fi에서 요청을 시작하고 엘리베이터로 걸어가면 휴대폰은 셀룰러 네트워크로 전환됩니다. TCP를 사용하면 진행 중이던 요청은 실패하고 클라이언트 측 재시도 로직(재시도 로직이 있죠, 그렇죠?)이 전체 재연결과 함께 작동합니다. QUIC을 사용하면 연결이 장치를 따라 새 네트워크로 이동합니다. 클라이언트 로그에는 시간 초과 오류가 줄어들고, 불완전하게 완료된 쓰기에 대해 고민할 필요도 줄어듭니다.
실패 모드가 없는 멀티플렉싱
REST API의 경우, 클라이언트가 많은 요청을 병렬로 보낼 때(예: 15개의 위젯을 업데이트하는 대시보드, 일괄 업데이트를 푸시하는 동기화 엔진) HOL 차단 수정이 가장 중요합니다. 깨끗한 네트워크에서는 HTTP/2와 HTTP/3의 성능이 거의 동일합니다. 패킷 손실이 1~2% 정도 발생하면(혼잡한 회의실 Wi-Fi, 지하철 셀룰러 등), HTTP/3는 병렬 요청을 독립적으로 유지하는 반면 HTTP/2는 요청들을 동시에 지연시킵니다.
gRPC는 현재 대부분 HTTP/2에 머무릅니다
gRPC는 설계상 HTTP/2에 묶여 있습니다. 와이어 계약은 HTTP/2 프레이밍 및 트레일러에 의존합니다. gRPC 생태계는 HTTP/3 매핑을 표준화하지 않았으며, 주류 구현체(Go, Java, Python, Node)는 이를 제공하지 않습니다. .NET의 Kestrel 서버는 실험적 기능으로 HTTP/3를 통해 gRPC를 제공할 수 있지만, 이는 예외로 간주해야 합니다. 내부 API 성능을 위해 gRPC 및 HTTP/2에 의존하는 아키텍처를 가지고 있다면, 올해 HTTP/3 마이그레이션을 계획할 필요는 없습니다.
스트리밍 및 실시간 트래픽
Server-Sent Events는 일반적인 장기 HTTP 응답이므로 HTTP/3에서도 변경 없이 작동합니다. WebSockets는 더 까다롭습니다. WebSocket 업그레이드는 TCP용으로 설계되었으며, 그 HTTP/3 등가물(RFC 9220 및 새로 등장하는 WebTransport API)은 지원이 미비합니다. 실시간 기능을 위해 WebSockets와 일반 HTTP를 저울질하고 있다면, HTTP/3 가용성이 아직 결정을 좌우해서는 안 됩니다.
솔직하게: HTTP/3가 도움이 되지 않을 때
대부분의 API 지연 시간 문제는 전송 프로토콜과 관련이 없습니다. 인덱싱되지 않은 데이터베이스 쿼리 때문에 엔드포인트가 400ms가 걸린다면, HTTP/3는 그 느린 응답을 60ms 더 빨리 전달할 것입니다. 캐싱, 페이로드 설계, N+1 쿼리, 연결 재사용이 실제 API 성능을 지배하며, 전송에 대해 생각하기 전에 이러한 문제들을 먼저 해결해야 합니다. 구조화된 API 성능 테스트는 일반적으로 프로토콜 업그레이드보다 10배 더 큰 성과를 가져올 것입니다.
HTTP/3는 특정 조건에서 빛을 발합니다.
- 높은 지연 시간 링크: 왕복 시간을 절약하는 것이 고정된 비율의 이득을 가져올 때
- 손실이 많은 네트워크: HOL 차단 제거가 효과를 극대화할 때
- 세션 도중 네트워크를 전환하는 모바일 클라이언트
- 적은 수의 긴 연결 대신 많은 수의 짧은 연결을 사용할 때
안정적인 네트워크를 통해 동일 지역 내 서버에서 소비되는 일반적인 JSON API의 경우, 그 차이는 벤치마크에서는 측정 가능하지만 사용자에게는 보이지 않습니다. 두 가지 실용적인 참고 사항: 일부 기업 네트워크에서는 UDP 포트 443이 차단될 수 있으며(클라이언트는 자동으로 HTTP/2로 폴백하므로 문제가 발생하지 않음), QUIC의 사용자 공간 암호화는 현재 튜닝된 커널 TCP보다 연결당 더 많은 서버 CPU 비용을 발생시킵니다.
현재 지원 현황: 오늘날 누가 HTTP/3를 사용하고 있는가
채택률은 대부분의 백엔드 개발자들이 생각하는 것보다 앞서 있습니다.
- 브라우저: Chrome, Edge, Firefox, Safari 모두 HTTP/3가 기본적으로 활성화되어 제공됩니다.
- CDN 및 엣지: Cloudflare, Fastly, Akamai, CloudFront가 이를 지원합니다. Cloudflare에서는 토글 스위치로 설정할 수 있습니다. 대부분의 팀에게는 이것이 실용적인 방법입니다. 즉, 엣지에서 HTTP/3를 종료하고, 엣지에서 오리진까지는 HTTP/1.1 또는 HTTP/2를 유지하는 것입니다.
- 서버: Nginx는 1.25 버전에서
listen 443 quic;지시어를 통해 실험적인 HTTP/3를 추가했습니다. Caddy는 기본적으로 이를 활성화합니다. LiteSpeed와 HAProxy는 지원합니다. Apache httpd는 지원하지 않습니다. - 런타임: Node.js는 안정적인 내장 HTTP/3 서버 지원이 없으며, 이는 엣지 터미네이션이 일반적인 배포 방식인 또 다른 이유입니다.
- curl: HTTP/3 지원 TLS 스택에 대해 빌드된 경우
--http3플래그와 함께 지원됩니다. 어떤 빌드에 포함되는지는 curl HTTP/3 문서를 참조하세요.
API가 HTTP/3를 제공하는지 확인하는 방법
검색은 Alt-Svc 응답 헤더를 통해 작동합니다. HTTP/3를 광고하는 서버는 첫 번째 (HTTP/2) 요청에 다음과 같이 응답합니다.
alt-svc: h3=":443"; ma=86400
이는 클라이언트에게 다음을 알려줍니다. "이 서비스는 다음 24시간 동안 UDP 포트 443을 통해 HTTP/3로 이용 가능합니다." curl로 확인해보세요.
curl -sI https://www.cloudflare.com | grep -i alt-svc
# alt-svc: h3=":443"; ma=86400
HTTP/3를 통해 직접 요청하려면 (HTTP/3가 활성화된 curl 빌드가 필요합니다):
curl --http3 -I https://cloudflare-quic.com
# HTTP/3 200
상태 라인이 HTTP/2 대신 HTTP/3로 보고됩니다. Chrome 개발자 도구에서는 네트워크 탭을 열고, 열 헤더를 마우스 오른쪽 버튼으로 클릭한 다음, 프로토콜 열을 활성화하고, API 호출 옆에서 h3를 찾으세요. 프로덕션 환경에서는 협상된 프로토콜을 액세스 로그에 추가하세요. h2와 h3 트래픽 간의 분할은 얼마나 많은 클라이언트가 이점을 얻고 있는지 알려줍니다.
전송 계층을 확인하는 동안, 동작도 함께 확인하세요. 동일한 엔드포인트에 Apidog를 사용하여 상태 코드, 응답 스키마, 지연 시간 예산을 검증하세요. 기본 API 계약이 깨져 있다면 전송 계층의 이점은 무의미하며, 프로토콜 변경으로 해결할 수 없는 계층이 바로 계약 확인 계층입니다. 엣지에서 HTTP/3를 활성화하기 전후에 Apidog를 무료로 다운로드하여 동일한 테스트 스위트를 실행해 보세요. 모바일 네트워크에서의 응답 시간 차이가 벤치마크 헤드라인이 아닌 실제 답변이 될 것입니다.
자주 묻는 질문
HTTP/3는 HTTP/2보다 빠른가요?
깨끗하고 지연 시간이 짧은 네트워크에서는 거의 차이가 없습니다. 손실이 많거나 지연 시간이 긴 네트워크에서는 종종 눈에 띄게 빠릅니다. HTTP/3는 핸드셰이크 왕복 시간을 절약하고, 단일 손실 패킷이 모든 멀티플렉스 요청을 더 이상 지연시키지 않기 때문입니다. 이점을 주장하기 전에 자신의 트래픽 프로파일로 측정해 보세요. 그리고 HTTP/2도 여전히 훌륭하다는 것을 기억하세요. 거기서 연결 오류가 발생한다면, 보통 프로토콜 제한보다는 SSLV3_ALERT_HANDSHAKE_FAILURE 문제와 같은 TLS 계층 문제입니다.
HTTP/3는 TCP를 사용하나요?
아니요. HTTP/3는 QUIC 위에서 실행되며, QUIC은 일반적으로 443번 포트의 UDP 위에서 실행됩니다. QUIC은 TCP가 제공하던 신뢰성, 순서 지정, 혼잡 제어 기능을 스트림별로 사용자 공간에서 재구현합니다. 네트워크에서 UDP 443이 차단된 경우, 클라이언트는 자동으로 TCP를 통한 HTTP/2로 폴백합니다.
HTTP/3를 위해 API 코드를 변경해야 하나요?
거의 그럴 필요가 없습니다. HTTP 의미론은 변경되지 않았습니다. 동일한 메서드, 헤더, 상태 코드, 본문을 사용합니다. 작업은 인프라(CDN, 로드 밸런서 또는 서버에서 활성화)에 있으며, 한 가지 설계 검사가 필요합니다. 0-RTT 초기 데이터가 멱등성 요청으로 제한되는지 확인해야 합니다.
HTTP/3를 통해 gRPC를 사용할 수 있나요?
현재로서는 대부분 아니요. gRPC의 와이어 포맷은 HTTP/2에 묶여 있으며, 주류 gRPC 라이브러리는 HTTP/3 전송을 제공하지 않습니다. .NET은 실험적 지원을 제공합니다. gRPC 서비스는 HTTP/2에 유지하고, HTTP/3가 먼저 효과를 발휘할 수 있는 공용, 브라우저 대면, 모바일 대면 REST 엔드포인트에 HTTP/3를 도입하세요.
