HTTP/3는 왜 TCP를 버리고 QUIC을 선택했을까

HTTP/2의 한계였던 HOL Blocking부터 QUIC의 스트림 구조, TLS 1.3, Connection Migration까지 HTTP/3의 설계를 살펴봅니다

공유하기
HTTP/3는 왜 TCP를 버리고 QUIC을 선택했을까

HTTP는 오랫동안 TCP 위에서 동작했다.

HTTP/1.1도 TCP를 사용했고, HTTP/2 역시 TCP 위에 올라갔다. HTTP/2에서 스트림과 멀티플렉싱이 도입되면서 이전보다 훨씬 효율적으로 여러 요청을 처리할 수 있게 됐지만, 전송 계층 자체는 그대로였다.

HTTP/3에서는 이 구조가 바뀌었다.

HTTP/1.1  ─┐
HTTP/2    ─┴─ TCP ─ IP

HTTP/3 ─ QUIC ─ UDP ─ IP

HTTP/3는 TCP 대신 QUIC을 사용하고, QUIC은 UDP 위에서 동작한다.

처음 보면 다소 이상한 선택처럼 보인다. TCP는 이미 신뢰성 있는 전송, 순서 보장, 재전송, 흐름 제어, 혼잡 제어를 제공한다. 반면 UDP는 데이터그램을 전달하는 것 외에는 거의 아무것도 해주지 않는다.

그런데 HTTP/3는 오히려 이 단순한 UDP 위에 새로운 전송 프로토콜을 다시 만들었다.

왜 그랬을까?


HTTP/2가 해결한 것과 해결하지 못한 것

HTTP/1.1에서는 한 연결을 효율적으로 활용하기가 쉽지 않았다.

브라우저가 한 페이지를 표시하려면 HTML 하나만 받으면 끝나는 게 아니다.

index.html
style.css
app.js
logo.webp
font.woff2
api/user

수많은 리소스를 동시에 요청해야 한다.

HTTP/1.1에서는 이를 처리하기 위해 같은 호스트에 여러 TCP 연결을 만들거나 pipelining 같은 방식을 사용할 수 있었지만, 구조적인 한계가 있었다.

HTTP/2는 이 문제를 Stream과 Multiplexing으로 해결했다.

하나의 TCP 연결 안에 여러 논리적 스트림을 만들고, 각 스트림의 데이터를 프레임 단위로 섞어서 보낸다.

TCP Connection

┌─ Stream 1 : HTML
├─ Stream 3 : CSS
├─ Stream 5 : JavaScript
└─ Stream 7 : API

HTTP 레벨에서는 서로 다른 요청을 동시에 처리할 수 있게 된 것이다.

하지만 여기에는 중요한 문제가 하나 남아 있었다.

HTTP/2의 스트림은 독립적이지만, 그 아래의 TCP는 스트림이라는 개념을 모른다.

TCP가 보는 것은 하나의 연속된 바이트 스트림뿐이다.


TCP Head-of-Line Blocking

TCP의 가장 중요한 특징 중 하나는 신뢰성 있는 순서 보장이다.

송신자가 다음 데이터를 보냈다고 해보자.

1 → 2 → 3 → 4 → 5

그런데 네트워크에서 3번 데이터가 유실됐다.

1 → 2 → X → 4 → 5

4와 5가 먼저 도착했다고 해도 TCP는 애플리케이션에 이를 마음대로 넘길 수 없다.

3이 다시 도착할 때까지 기다려야 한다.

1 → 2 → [ 3 재전송 대기 ] → 4 → 5

이런 현상을 Head-of-Line Blocking, 흔히 HOL Blocking이라고 부른다.

TCP 입장에서는 당연한 동작이다. 애플리케이션에는 송신자가 보낸 것과 동일한 순서의 바이트 스트림을 제공해야 하기 때문이다.

문제는 HTTP/2와 결합했을 때 나타난다.

HTTP/2에서는 논리적으로 다음 세 스트림이 서로 독립적일 수 있다.

Stream A : HTML
Stream B : CSS
Stream C : API

하지만 이 데이터들이 모두 하나의 TCP 연결을 통과한다.

따라서 Stream B에 속한 데이터를 운반하던 TCP 세그먼트가 유실됐더라도, 뒤에 도착한 Stream A와 Stream C의 데이터까지 TCP 수신 버퍼에서 기다리게 될 수 있다.

HTTP/2 Streams

A ────────────────▶
B ─────── X ──────▶
C ────────────────▶

        ↓

       TCP

1 ─ 2 ─ X ─ 4 ─ 5
        ↑
     여기서 대기

HTTP/2는 HTTP 계층의 HOL Blocking 상당 부분을 해결했지만, TCP 계층의 HOL Blocking까지 없앨 수는 없었다.

이 차이가 HTTP/3를 이해하는 핵심이다.


그렇다면 TCP를 고치면 되지 않을까?

여기서 자연스럽게 이런 의문이 생긴다.

문제의 원인이 TCP라면 TCP 자체를 개선하면 되지 않을까?

프로토콜만 놓고 보면 가능하다.

문제는 인터넷이 단순히 두 컴퓨터를 연결한 네트워크가 아니라는 점이다.

클라이언트와 서버 사이에는 수많은 장비가 존재한다.

Client
  ↓
Home Router
  ↓
NAT
  ↓
ISP
  ↓
Firewall
  ↓
Load Balancer
  ↓
Server

그리고 이 중 상당수는 TCP 트래픽에 대해 특정한 동작을 기대한다.

이런 장비를 보통 Middlebox라고 부른다.

NAT, 방화벽, 프록시, 로드밸런서 등이 대표적이다.

오랫동안 사용된 프로토콜은 좋은 점도 있지만, 반대로 기존 구현과 장비가 너무 많아지면서 프로토콜을 크게 변경하기 어려워지는 문제가 생긴다.

이를 흔히 프로토콜 ossification, 즉 프로토콜 경직화라고 표현한다.

TCP에 새로운 기능을 추가하더라도 중간 장비가 예상하지 못한 옵션이나 동작을 차단하거나 잘못 처리할 수 있다.

게다가 TCP 스택은 보통 운영체제 커널에 구현돼 있다.

즉, 전송 프로토콜의 기능을 개선하려면 브라우저만 업데이트해서 끝나는 것이 아니라 운영체제의 TCP 구현까지 영향을 받을 수 있다.

QUIC은 이 문제를 피하는 방향으로 설계됐다.


QUIC은 UDP를 빠르게 만든 프로토콜이 아니다

QUIC을 설명할 때 흔히 나오는 표현이 있다.

HTTP/3는 UDP를 사용하기 때문에 빠르다.

정확한 설명은 아니다.

UDP 자체가 TCP보다 특별히 빠른 전송 알고리즘을 갖고 있는 것은 아니다.

UDP에는 애초에 그런 알고리즘이 거의 없다.

UDP가 제공하는 것

- Source Port
- Destination Port
- Length
- Checksum
- Datagram 전달

패킷이 사라졌는지, 순서가 뒤바뀌었는지, 상대방이 데이터를 받을 준비가 되었는지에 대해서 UDP는 크게 신경 쓰지 않는다.

QUIC은 이 UDP 위에서 필요한 기능을 직접 구현한다.

대표적으로 다음 기능이 포함된다.

결국 구조는 대략 이렇게 볼 수 있다.

HTTP/3
   ↓
QUIC
 ├─ Stream
 ├─ Reliability
 ├─ Flow Control
 ├─ Congestion Control
 ├─ Loss Recovery
 ├─ TLS 1.3
 └─ Connection Management
   ↓
UDP
   ↓
IP

UDP는 여기서 QUIC 패킷을 인터넷에 실어 나르는 호환 가능한 운반 수단에 가깝다.


QUIC에서는 스트림이 전송 계층부터 독립적이다

HTTP/2와 HTTP/3의 차이 중 가장 중요한 부분이다.

HTTP/2에서는 HTTP 계층에만 스트림이 있다.

HTTP/2

Stream 1 ┐
Stream 3 ├─ HTTP/2
Stream 5 ┘
           ↓
        TCP Byte Stream

하지만 QUIC은 전송 프로토콜 자체가 여러 스트림을 이해한다.

HTTP/3
   ↓
QUIC
 ├─ Stream 0
 ├─ Stream 4
 └─ Stream 8
   ↓
UDP

따라서 특정 스트림에서 데이터가 손실됐을 때 다른 스트림까지 같은 이유로 멈출 필요가 없다.

Stream A ─────────────────────▶

Stream B ───── X ── 재전송 ───▶

Stream C ─────────────────────▶

Stream B는 손실된 데이터를 기다리지만 Stream A와 Stream C는 계속 진행할 수 있다.

여기서 한 가지 주의할 점이 있다.

QUIC에서 HOL Blocking이 완전히 사라지는 것은 아니다.

같은 QUIC 스트림 안에서는 여전히 데이터 순서가 보장된다. 따라서 해당 스트림에서 앞부분의 데이터가 유실되면 그 스트림의 뒤쪽 데이터는 기다려야 한다.

즉,

TCP + HTTP/2
패킷 손실 → 연결의 여러 스트림에 영향 가능

QUIC + HTTP/3
패킷 손실 → 일반적으로 해당 스트림에 영향 제한

이라고 이해하는 편이 정확하다.


QUIC의 Packet Number는 TCP의 Sequence Number와 다르다

조금 더 안으로 들어가 보면 QUIC의 손실 복구 방식에서도 TCP와 차이가 보인다.

TCP에서는 바이트 위치를 나타내는 Sequence Number를 사용한다.

반면 QUIC은 전송되는 각 패킷에 Packet Number를 부여한다.

그리고 중요한 특징이 하나 있다.

재전송된 QUIC 패킷은 새로운 Packet Number를 가진다.

예를 들어 다음과 같은 패킷이 있었다고 해보자.

Packet 20
Packet 21
Packet 22  ← 손실
Packet 23

TCP처럼 22번 패킷 자체를 그대로 다시 보내는 개념보다는, 손실된 패킷에 포함돼 있던 필요한 프레임을 새로운 패킷에 다시 실어 보낸다.

Packet 24
└─ Packet 22에 들어 있던 STREAM 데이터 포함

이 구조 덕분에 어떤 전송이 원본이고 어떤 전송이 재전송인지 구분하기 쉬워지고, RTT 측정이나 손실 판단 과정에서 생길 수 있는 모호함도 줄어든다.

QUIC에서는 ACK 역시 단순히 마지막으로 받은 번호 하나만 전달하는 방식이 아니라 여러 Packet Number 범위를 표현할 수 있다.

Received:

100 101 102   104 105   108

ACK Range:

100-102
104-105
108

이런 정보는 QUIC의 손실 감지 알고리즘이 어떤 패킷이 실제로 유실됐는지 판단하는 데 사용된다.


QUIC은 암호화를 선택 사항으로 두지 않는다

TCP와 TLS는 원래 서로 별개의 프로토콜이다.

기존 HTTPS를 단순화하면 대략 다음과 같다.

HTTP
 ↓
TLS
 ↓
TCP
 ↓
IP

먼저 TCP 연결을 수립한다.

Client                       Server

   SYN ---------------------->

       <---------------- SYN + ACK

   ACK ---------------------->

그 다음 TLS Handshake를 진행한다.

그 이후에야 암호화된 HTTP 데이터를 주고받는다.

QUIC은 처음 설계될 때부터 TLS 1.3을 전송 프로토콜에 통합했다.

HTTP/3에서는 QUIC 사용 시 암호화되지 않은 일반 QUIC 연결을 선택해서 사용하는 식의 동작을 하지 않는다.

대부분의 QUIC 패킷 내용 역시 암호화된다.

이는 단순한 보안 기능 이상의 의미를 가진다.

중간 장비가 QUIC 내부의 전송 정보를 보고 임의로 동작하기 어려워지기 때문에, 앞서 언급한 프로토콜 경직화 문제를 줄이는 효과도 있다.


연결 수립에 필요한 RTT를 줄인다

RTT는 Round Trip Time, 즉 클라이언트에서 서버로 갔다가 다시 돌아오는 데 걸리는 시간을 의미한다.

서울에서 가까운 서버라면 RTT가 몇 ms 수준일 수도 있지만, 지리적으로 먼 서버나 모바일 네트워크에서는 수십~수백 ms가 될 수 있다.

전통적인 TCP + TLS 연결에서는 여러 단계가 필요했다.

TCP Handshake
      ↓
TLS Handshake
      ↓
HTTP Request

TLS 1.3에서는 과거 버전보다 Handshake가 크게 단축됐지만, TCP 연결 수립 자체가 먼저 필요하다는 구조는 그대로 남는다.

QUIC에서는 전송 계층의 연결 설정과 TLS Handshake를 결합할 수 있다.

QUIC + TLS 1.3
      ↓
HTTP/3

일반적인 새 연결은 1-RTT로 연결을 준비할 수 있다.

이전에 연결한 적이 있는 서버와 세션 정보를 재사용할 경우에는 0-RTT도 가능하다.

0-RTT에서는 첫 번째 패킷부터 애플리케이션 데이터를 함께 보낼 수 있다.

다만 0-RTT 데이터에는 중요한 제약이 있다.

0-RTT 데이터는 공격자가 이전 트래픽을 재전송하는 Replay Attack에 대한 별도의 고려가 필요하다.

따라서 서버는 상태를 변경하는 요청처럼 재실행에 민감한 작업을 0-RTT로 처리할 때 주의해야 한다.

빠른 연결이라는 장점만 보고 무조건 사용할 수 있는 기능은 아니다.


QUIC에는 하나가 아니라 여러 Packet Number Space가 있다

QUIC의 Handshake를 조금 더 자세히 보면 패킷도 단계별로 구분된다.

대표적으로 다음 세 Packet Number Space가 존재한다.

Initial
Handshake
Application Data

연결 초기에 사용하는 키와 Handshake 이후 사용하는 키가 다르기 때문에, 패킷 번호 공간도 분리된다.

대략적인 흐름은 다음과 같다.

Client                                  Server

Initial
ClientHello ---------------------------->

                         Initial / Handshake
                    <----- ServerHello, TLS ...

Handshake
TLS Finished --------------------------->

Application Data
HTTP/3 ================================>

실제 QUIC Handshake는 TCP 위에서 TLS를 따로 수행하는 것과 달리 TLS 메시지를 QUIC의 CRYPTO frame을 통해 운반한다.

즉 QUIC과 TLS가 단순히 위아래로 쌓인 관계라기보다 연결 설정 과정에서 밀접하게 결합돼 있다.


Connection ID: IP가 달라져도 같은 연결일 수 있다

TCP 연결은 일반적으로 다음 4개의 값으로 식별된다.

Source IP
Source Port
Destination IP
Destination Port

이를 흔히 4-tuple이라고 한다.

문제는 네트워크 환경이 바뀌면 이 값도 바뀔 수 있다는 것이다.

예를 들어 스마트폰이 Wi-Fi를 사용하고 있다고 해보자.

Wi-Fi
192.168.0.12

밖으로 이동하면서 5G로 전환되면 클라이언트의 IP와 네트워크 경로가 바뀐다.

Wi-Fi
   ↓
5G

IP 변경
Port 변경 가능
Network Path 변경

TCP 관점에서는 기존 연결과 동일한 4-tuple을 유지할 수 없기 때문에 연결을 계속 사용하는 데 제약이 있다.

QUIC은 이를 해결하기 위해 Connection ID를 사용한다.

연결을 IP 주소와 포트에만 묶지 않고 논리적인 연결 식별자를 별도로 두는 것이다.

Wi-Fi

IP A : Port A
    │
    │ Network Change
    ▼
5G

IP B : Port B

QUIC Connection ID
       유지

덕분에 네트워크 인터페이스나 IP가 변경됐더라도 조건이 맞으면 기존 연결을 이어갈 수 있다.

이를 Connection Migration이라고 한다.

모바일 환경에서 QUIC이 특히 유용한 이유 중 하나다.


Path Validation

그렇다고 클라이언트가 다른 IP에서 같은 Connection ID를 들고 왔다고 해서 서버가 무조건 믿는 것은 아니다.

새로운 경로가 실제 클라이언트의 경로인지 확인하는 절차가 필요하다.

QUIC에는 이를 위한 프레임이 있다.

PATH_CHALLENGE
PATH_RESPONSE

서버 또는 클라이언트가 새로운 네트워크 경로를 발견하면 PATH_CHALLENGE를 보내고, 상대방이 이에 대응하는 PATH_RESPONSE를 반환하는 방식으로 경로를 검증할 수 있다.

즉 Connection Migration은 단순한 식별자 재사용이 아니라 새로운 경로에 대한 검증까지 포함한 메커니즘이다.


QUIC의 스트림은 단방향과 양방향으로 나뉜다

QUIC 스트림에는 크게 두 종류가 있다.

Bidirectional Stream

양쪽 모두 데이터를 전송할 수 있다.

Client ⇄ Server

HTTP 요청과 응답처럼 양쪽 통신이 필요한 경우 활용된다.

Unidirectional Stream

한쪽에서만 데이터를 전송한다.

Client ───▶ Server

또는

Client ◀─── Server

HTTP/3는 이런 스트림을 이용해 데이터뿐 아니라 제어 정보도 분리한다.

HTTP/2에서는 프레임들이 하나의 TCP 연결 위에서 처리됐다면, HTTP/3에서는 QUIC의 스트림 구조를 적극적으로 활용한다.


HTTP/3에서 헤더 압축도 바뀌었다

HTTP/2에서는 HTTP 헤더 압축을 위해 HPACK을 사용한다.

문제는 HTTP/3에서 그대로 HPACK을 사용하기 어렵다는 점이다.

HPACK은 압축 효율을 높이기 위해 동적인 헤더 테이블을 사용하며, 이전에 처리된 헤더 상태에 의존할 수 있다.

하지만 HTTP/3의 목표는 스트림 사이의 불필요한 의존성을 줄이는 것이다.

그래서 HTTP/3에서는 QPACK이라는 새로운 헤더 압축 방식을 사용한다.

HTTP/2
└─ HPACK

HTTP/3
└─ QPACK

QPACK은 QUIC의 독립적인 스트림 특성을 고려해 설계됐으며, 헤더 압축 상태 때문에 다른 스트림 전체가 불필요하게 막히는 문제를 줄이는 방향으로 만들어졌다.

HTTP/3는 단순히 HTTP/2를 QUIC 위에 그대로 올린 프로토콜이 아니다.

전송 계층이 바뀌면서 HTTP 계층의 일부 설계도 함께 변경됐다.


흐름 제어도 스트림과 연결 두 단계로 나뉜다

QUIC은 수신자가 처리할 수 있는 양보다 지나치게 많은 데이터를 받지 않도록 Flow Control을 제공한다.

재미있는 부분은 흐름 제어가 두 수준에서 동작한다는 것이다.

Connection-level Flow Control
            +
Stream-level Flow Control

즉 전체 연결에서 받을 수 있는 데이터 양을 제한할 수도 있고, 개별 스트림마다 별도의 한계를 설정할 수도 있다.

대표적인 QUIC 프레임에는 다음과 같은 것들이 있다.

MAX_DATA
MAX_STREAM_DATA
MAX_STREAMS

이를 이용해 수신자는 송신자에게

전체 연결에서는 여기까지 보내도 된다.

또는

이 스트림에서는 여기까지 보내도 된다.

는 정보를 전달한다.

멀티플렉싱을 전송 계층에서 구현한 만큼 흐름 제어 역시 스트림 단위로 세밀하게 관리할 수 있는 것이다.


혼잡 제어는 여전히 필요하다

QUIC이 UDP 위에서 동작한다고 해서 마음대로 패킷을 쏟아낼 수 있는 것은 아니다.

인터넷 전체가 안정적으로 동작하려면 네트워크가 처리할 수 있는 수준에 맞춰 전송 속도를 조절해야 한다.

따라서 QUIC에도 TCP와 마찬가지로 Congestion Control이 필요하다.

QUIC 표준은 전송 과정에서 혼잡 제어가 동작할 수 있도록 설계돼 있으며, 구현체는 다양한 알고리즘을 사용할 수 있다.

예를 들어 환경에 따라 CUBIC이나 BBR 계열 알고리즘을 적용할 수 있다.

중요한 차이는 QUIC의 전송 로직이 대체로 User Space에 존재하기 때문에 이런 알고리즘을 업데이트하거나 실험하기가 상대적으로 쉽다는 점이다.


User Space에서 구현한다는 것의 의미

TCP는 일반적으로 운영체제 커널에 구현된다.

Application
    ↓
Socket API
    ↓
Kernel TCP
    ↓
IP

따라서 TCP 자체를 크게 바꾸려면 운영체제의 네트워크 스택까지 영향을 받는다.

반면 일반적인 QUIC 구현은 다음과 같은 구조를 사용할 수 있다.

Browser / Server

HTTP/3
  ↓
QUIC
  ↓
UDP Socket
──────────────
Kernel
  ↓
UDP
  ↓
IP

QUIC의 상당 부분이 User Space 라이브러리로 구현될 수 있기 때문에 브라우저나 서버 소프트웨어 업데이트만으로 새로운 구현을 배포할 수 있다.

이는 QUIC의 성능 자체만큼 중요한 설계상의 장점이다.

프로토콜을 인터넷 전체에 배포하는 속도가 훨씬 빨라질 수 있기 때문이다.


UDP를 선택한 진짜 이유

그렇다면 QUIC은 왜 완전히 새로운 IP 전송 프로토콜을 만들지 않고 UDP 위에 올라갔을까?

예를 들어 다음처럼 만드는 것도 이론적으로는 가능하다.

HTTP/3
  ↓
QUIC
  ↓
IP

TCP는 IP Protocol Number 6, UDP는 17을 사용한다.

QUIC도 별도의 새로운 IP Protocol Number를 받아 사용할 수 있었을 것이다.

하지만 그렇게 하면 현실적인 문제가 생긴다.

인터넷 곳곳의 NAT와 방화벽은 TCP와 UDP는 잘 알고 있지만, 알 수 없는 새로운 전송 프로토콜은 차단할 가능성이 높다.

UDP를 사용하면 기존 네트워크 인프라가 이미 이해하고 있는 형태로 패킷을 전달할 수 있다.

즉 UDP는 단순히 가볍기 때문에 선택된 것이 아니다.

현재 인터넷을 통과할 수 있는 현실적인 확장 지점이었기 때문이다.


QUIC에도 단점은 있다

QUIC이 모든 면에서 TCP보다 우월한 것은 아니다.

우선 QUIC의 많은 기능이 User Space에서 처리되기 때문에 구현 초기에는 CPU 사용량이 TCP보다 높아질 수 있었다.

TCP는 오랫동안 운영체제와 네트워크 카드에서 최적화되어 왔다.

예를 들어 NIC나 커널에는 TCP를 위한 여러 하드웨어 및 소프트웨어 오프로딩 기능이 존재한다.

QUIC은 상대적으로 새로운 프로토콜이기 때문에 이런 최적화가 동일한 수준으로 적용되지 않는 환경도 있다.

UDP를 제한하는 네트워크도 여전히 존재한다.

기업 네트워크나 오래된 방화벽에서 UDP 443 트래픽을 차단하면 QUIC 연결이 실패할 수 있다.

이 경우 클라이언트는 보통 TCP 기반 HTTP/2 등으로 fallback한다.

HTTP/3 over QUIC
       ↓ 실패
HTTP/2 over TCP

따라서 HTTP/3 도입이 기존 HTTP 프로토콜을 즉시 없애는 것은 아니다.


HTTP/3의 핵심은 'UDP가 더 빠르다'가 아니다

HTTP/3를 단순히 다음과 같이 기억하기 쉽다.

HTTP/2 = TCP
HTTP/3 = UDP

틀린 표현은 아니지만 핵심을 많이 놓친 설명이다.

좀 더 정확하게 표현하면 다음과 같다.

HTTP/2

HTTP Multiplexing
       ↓
단일 TCP Byte Stream
       ↓
TCP 수준 HOL Blocking


HTTP/3

HTTP Multiplexing
       ↓
QUIC Streams
       ↓
스트림별 독립적인 손실 복구

HTTP/3에서 중요한 변화는 TCP를 UDP로 교체한 것이 아니라, HTTP가 필요로 하는 전송 계층 자체를 다시 설계했다는 것이다.

QUIC은 이를 위해

UDP는 그 설계를 현실의 인터넷 위에 배포하기 위한 기반이었다.


마치며

HTTP/2는 상당히 잘 설계된 프로토콜이었다.

하지만 HTTP 계층을 아무리 개선해도 그 아래 TCP가 가진 특성까지 바꿀 수는 없었다.

하나의 TCP 바이트 스트림에서 발생하는 Head-of-Line Blocking, TCP와 TLS가 분리되어 있던 연결 구조, IP 주소와 포트에 강하게 연결된 세션, 그리고 기존 인터넷 장비 때문에 TCP 자체를 빠르게 변경하기 어려운 문제까지.

HTTP/3에서는 결국 HTTP만 수정하지 않았다.

전송 계층까지 다시 설계했다.

그 결과가 QUIC이다.

HTTP/2

HTTP/2
  ↓
TLS
  ↓
TCP
  ↓
IP


HTTP/3

HTTP/3
  ↓
QUIC
 ├─ TLS 1.3
 ├─ Streams
 ├─ Loss Recovery
 ├─ Flow Control
 └─ Connection Migration
  ↓
UDP
  ↓
IP

그래서 HTTP/3를 이해할 때 **“TCP보다 UDP가 빨라서 바뀌었다”**라고 보는 것보다는,

“TCP 위에서는 해결하기 어려웠던 문제를 해결하기 위해 QUIC이라는 새로운 전송 계층을 만들었고, 이를 기존 인터넷에 배포하기 위해 UDP를 기반으로 삼았다”

라고 이해하는 편이 훨씬 정확하다.