HTTP/2
HTTP/2의 바이너리 프레이밍·스트림 다중화·HPACK을 익히고 HTTP/1.1 병목과 남은 TCP HOL 문제를 이해합니다.
9장에서 HTTP/1.1의 요청-응답 구조를 살펴보았습니다.
HTTP/1.1은 웹의 폭발적 성장을 이끌었지만, 현대 웹 페이지의 복잡도가 증가하면서 성능 한계가 드러났습니다.
하나의 웹 페이지가 수십 개의 CSS, JavaScript, 이미지 파일을 필요로 하는 상황에서, HTTP/1.1의 구조적 문제가 병목이 되기 시작했습니다.
HTTP/1.1의 한계
HTTP/1.1의 대표적인 성능 한계는 Head-of-Line(HOL) Blocking입니다.
HTTP/1.1 파이프라이닝에서는 여러 요청을 먼저 보내도 응답은 요청 순서대로 보내야 하므로, 느린 앞 응답이 뒤 응답을 막습니다. 연결을 유지하는 keep-alive 자체가 파이프라이닝을 뜻하지는 않습니다. 브라우저는 연결 단위의 병목을 줄이려고 여러 TCP 연결을 열었습니다.
하지만 연결 수가 늘면 TCP/TLS 핸드셰이크, 혼잡 제어, 서버 부하가 함께 증가합니다.
개발자들은 HTTP/1.1의 연결 단위 병목을 줄이기 위해 다양한 최적화 패턴을 사용했습니다.
| 기법 | 방식과 HTTP/2에서의 판단 |
|---|---|
| 스프라이트 | 여러 이미지를 합쳐 요청을 줄입니다. 개별 캐시·유지보수 비용도 비교합니다. |
| 도메인 샤딩 | 여러 도메인에 연결을 나누지만 DNS/TLS 비용과 연결 재사용 손실이 생길 수 있습니다. |
| 인라이닝 | CSS/JS를 HTML에 넣어 초기 렌더링을 돕지만 독립 캐시를 잃습니다. |
| 파일 합치기 | JS/CSS 요청 수를 줄입니다. HTTP/2에서도 압축·실행·캐시 비용에 따라 선택합니다. |
HTTP/2는 이런 패턴이 의존하던 “요청 수 자체를 줄여야 한다”는 압박을 프로토콜 레벨에서 완화합니다.
다만 번들링, 인라이닝, 프리로드 같은 최적화는 애플리케이션 특성에 따라 여전히 선택적으로 사용됩니다.
바이너리 프레이밍과 멀티플렉싱
HTTP/2의 핵심 변화는 바이너리 프레이밍 계층(Binary Framing Layer)의 도입입니다.
HTTP 메시지를 텍스트 줄 단위로 그대로 흘려보내지 않고, HEADERS와 DATA 같은 프레임으로 나누어 여러 스트림을 하나의 연결 위에서 동시에 진행합니다.
이미 요청을 받은 서버의 응답 예시이며, 각 HEADERS에서 헤더 블록이 끝났다고 가정합니다. 헤더 블록이 CONTINUATION으로 이어지는 동안에는 다른 스트림의 프레임을 끼워 넣을 수 없습니다.
이미 요청을 받은 서버가 스트림 1과 3의 응답 프레임을 교차해 전송하는 허용 순서의 예시입니다. 스트림 3이 먼저 끝나며 END_STREAM은 DATA 프레임에 붙인 플래그입니다. TCP 패킷 경계나 필수 전송 순서를 뜻하지 않습니다.
END_STREAM은 별도 프레임이 아니라 해당 스트림의 송신 종료를 나타내는 플래그입니다. 그림의 프레임 경계는 TCP 패킷 경계와 같지 않습니다.
헤더 압축 (HPACK)
HTTP/1.1에서는 매 요청마다 비슷한 헤더가 반복 전송되었습니다.
HTTP/2는 HPACK으로 헤더 필드를 인덱스와 동적 테이블 중심으로 표현해 반복 전송량을 줄입니다.
민감한 값은 동적 테이블에 넣지 않는 표현도 있어, 압축 효율과 보안 고려를 함께 다룹니다.
| 표현 | 역할 |
|---|---|
| 정적 테이블 | 명세에 미리 정의된 이름·값을 인덱스로 참조합니다. 첫 요청을 저장해서 만드는 표가 아닙니다. |
| 동적 테이블 | 인덱싱하는 리터럴 표현으로 등록한 필드를 연결의 후속 헤더 블록에서 재사용합니다. |
| 등록하지 않는 표현 | without indexing / never indexed 리터럴은 동적 테이블에 추가하지 않습니다. |
서버 푸시
HTTP/2의 서버 푸시(Server Push)는 서버가 클라이언트의 후속 요청을 예측하여 PUSH_PROMISE로 미리 자원을 예약하고 보내는 기능입니다.
프로토콜에 정의된 기능이어도 클라이언트 지원과 성능 이득이 보장되는 것은 아닙니다. 캐시 중복과 예측 실패가 생길 수 있으며, Chrome은 106부터 서버 푸시를 기본 비활성화했습니다. 103 Early Hints나 preload는 자원 자체를 푸시하는 대신 클라이언트에 요청 힌트를 줍니다.
HTTP/2의 남은 문제
HTTP/2는 HTTP 메시지 단위의 HOL Blocking을 크게 줄였지만, TCP 계층의 HOL Blocking은 여전히 남습니다.
하나의 TCP 연결 위에 여러 HTTP/2 스트림이 올라가기 때문에, 패킷 손실이 발생하면 TCP가 순서를 복구할 때까지 뒤쪽 바이트 전달이 함께 지연될 수 있습니다.
| 버전 | 전송·압축·남는 지연 |
|---|---|
| HTTP/1.1 | TCP 위 텍스트 메시지이며 TLS는 선택입니다. 헤더 압축·서버 푸시는 없고, 파이프라인 응답 순서와 TCP 바이트 순서의 대기가 있습니다. |
| HTTP/2 | TCP 위 바이너리 프레임·HPACK·여러 스트림을 사용합니다. 응답 간 순서 제약은 줄지만 TCP HOL은 남습니다. TLS에서는 ALPN h2를 사용하며 서버 푸시는 명세와 구현 지원을 구분합니다. |
| HTTP/3 | UDP 위 QUIC 스트림·바이너리 프레임·QPACK을 사용하고 TLS 1.3이 통합됩니다. TCP HOL은 없지만 각 스트림의 순서 대기와 공유 혼잡 제어가 남습니다. 푸시는 명세에 있으며 재개 조건에 맞으면 0-RTT 데이터가 가능합니다. |
RFC 9113은 평문 HTTP/2의 prior knowledge 연결을 정의하지만, 과거 h2c Upgrade 방식은 폐기했습니다. TLS 유무와 프로토콜 협상 결과를 구분해 확인해야 합니다.
이 문제를 줄이기 위해 QUIC 기반의 HTTP/3가 등장했습니다.
다음 절에서 QUIC이 연결 수립, 손실 복구, 스트림 처리 방식을 어떻게 바꾸는지 자세히 다루겠습니다.