Http/2를 알아보자.

HTTP/2는 단순히 “더 빠른 HTTP”가 아닙니다.
HTTP/1.1의 한계를 극복하기 위해 근본적으로 재설계된 HTTP/2는 웹과 API 설계, 그리고 마이크로서비스 아키텍처(MSA) 환경에서 핵심 역할을 담당합니다.
이 글에서는 HTTP/2의 주요 기능—멀티플렉싱, 헤더 압축(HPACK), 서버 푸시, 요청 우선순위—와 이들 기능이 실제 프로토콜 동작에서 어떻게 구현되는지 구체적인 예시와 함께 살펴봅니다.
또한, gRPC가 왜 HTTP/2 위에서 동작하는지도 알아봅니다.
1. HTTP/1.1의 한계와 HTTP/2 도입 배경
HTTP/1.1의 문제점
직렬 처리: 하나의 TCP 커넥션에서 한 번에 하나의 요청/응답만 처리할 수 있음
헤드 오브 라인 블로킹(Head-of-Line Blocking): 한 커넥션 내에서 하나의 지연이 전체 순서를 막음
커넥션 과다 사용: 브라우저는 병렬성을 위해 도메인당 최대 6개 정도의 커넥션을 동시에 사용
중복 헤더 전송: 각 요청마다 비슷한 헤더가 반복되어 전송되어 대역폭 낭비 발생
Reflective Question:
왜 브라우저는 6개의 TCP 커넥션만 사용하려 할까요?
만약 수백 개의 요청을 처리해야 한다면 어떤 문제가 발생할지 생각해봅니다.
HTTP/2의 주요 목표
HTTP/2는 위의 문제를 해결하기 위해 설계되었습니다. 핵심 목표는 다음과 같습니다.
멀티플렉싱: 하나의 TCP 커넥션 위에서 동시에 여러 요청과 응답을 주고받을 수 있도록 함
헤더 압축: HPACK 알고리즘을 통해 중복 헤더 전송 오버헤드를 줄임
서버 푸시: 클라이언트가 아직 요청하지 않은 리소스를 서버가 미리 전송할 수 있도록 함
요청 우선순위: 각 요청에 우선순위 정보를 부여해 중요한 리소스가 먼저 전달되도록 함
2. HTTP/2 멀티플렉싱: 프로토콜 동작 예시
HTTP/2의 가장 큰 혁신은 멀티플렉싱입니다.
HTTP 메시지는 프레임(frame) 단위로 분할되고, 각 프레임에는 스트림 ID가 할당되어 여러 메시지가 같은 TCP 커넥션 내에서 동시에 interleave(교차 전송)됩니다.
실제 동작 예시
예를 들어, 브라우저가 /index.html
, /style.css
, /logo.png
를 요청한다고 가정해봅시다.
클라이언트 요청
Stream 1: HEADERS 프레임 – GET /index.html
Stream 3: HEADERS 프레임 – GET /style.css
Stream 5: HEADERS 프레임 – GET /logo.png
서버 응답 (프레임 단위 전송)
TCP 커넥션 위에서 프레임이 아래와 같이 전송될 수 있습니다.[Stream 1: HEADERS] → GET /index.html 요청 [Stream 3: HEADERS] → GET /style.css 요청 [Stream 5: HEADERS] → GET /logo.png 요청 [Stream 1: DATA] → /index.html 응답의 일부 [Stream 3: DATA] → /style.css 응답의 일부 [Stream 5: DATA] → /logo.png 응답의 일부 [Stream 1: DATA] → /index.html 응답의 나머지 ...
이와 같이 프레임 단위로 interleave되기 때문에, 한 스트림(예: /style.css
)의 전송이 지연되더라도 다른 스트림(예: /logo.png
)의 전송은 영향을 받지 않습니다.
Reflective Question:
HTTP/1.1과 비교했을 때, HTTP/2의 멀티플렉싱은
왜 하나의 TCP 커넥션 내에서 여러 요청을 동시에 처리할 수 있게 해줄까요?
3. HPACK: HTTP/2 헤더 압축 메커니즘
HTTP/2는 매 요청마다 반복되는 헤더 정보를 효율적으로 압축하기 위해 HPACK 알고리즘을 도입했습니다.
HPACK 동작 원리
정적 테이블: 자주 사용되는 헤더 필드(예:
:method
,:path
,:scheme
)를 미리 정의하여 인덱스 번호로 매핑동적 테이블: 클라이언트와 서버가 공유하는 캐시처럼 동작하며, 한 번 전송된 헤더를 저장하여 다음 요청부터는 인덱스 번호만 사용
예시
첫 번째 요청에서 클라이언트가 보내는 헤더:
:method: GET :path: /index.html :scheme: https user-agent: Mozilla/5.0 (예시)
- 이 때, 헤더 전체가 압축되어 전송됩니다.
이후 동일한
user-agent
헤더가 다시 사용될 경우:클라이언트는 동적 테이블에 이미 저장된 인덱스를 참조하여 해당 헤더를 인덱스 번호로만 전송합니다.
결과적으로 전송되는 데이터 크기가 현저히 줄어듭니다.
Reflective Question:
HTTP 요청마다 반복되는 헤더 전송의 오버헤드는 얼마나 큰 문제일까요?
HPACK은 이 문제를 어떻게 해결할 수 있을지 생각해보세요.
4. 서버 푸시(Server Push): 미리 전송하는 리소스
HTTP/2에서는 서버가 클라이언트 요청 없이도 리소스를 미리 전송할 수 있습니다. 이를 서버 푸시라고 합니다.
동작 예시
클라이언트 요청:
클라이언트가/index.html
을 요청합니다.서버 응답 준비:
서버는/index.html
에 대한 응답을 준비하는 동시에, HTML 내에 포함된/style.css
와/main.js
같은 리소스를 미리 전송할 필요가 있다고 판단합니다.PUSH_PROMISE 프레임 전송:
서버는 현재 Stream(예: Stream 1)에서PUSH_PROMISE
프레임을 보내며, “style.css를 곧 보내겠다”는 약속을 합니다.[Stream 1: PUSH_PROMISE] → "style.css will be pushed on Stream 2"
실제 푸시 응답 전송:
이후 서버는 새로운 Stream(예: Stream 2)에서 실제로/style.css
의 HEADERS와 DATA 프레임을 전송합니다.[Stream 2: HEADERS] → style.css 응답 헤더 [Stream 2: DATA] → style.css 파일 데이터
클라이언트 처리:
클라이언트는 PUSH_PROMISE 프레임을 받고, 리소스가 이미 캐시에 존재하면 RST_STREAM으로 해당 푸시 스트림을 거부할 수 있습니다.
Reflective Question:
서버 푸시는 예측이 맞을 경우 큰 성능 이점을 주지만,
클라이언트가 이미 해당 리소스를 가지고 있을 경우 어떤 문제가 발생할 수 있을까요?
5. HTTP/2 요청 우선순위: 스트림 간 작업 순서 최적화
HTTP/2는 각 요청(Stream)에 대해 우선순위 정보를 추가할 수 있습니다.
이 정보는 가중치(Weight, 1~256)와 의존성(Dependency)으로 구성되어, 클라이언트는 중요 리소스를 먼저 받도록 서버에 신호를 보낼 수 있습니다.
동작 예시
예를 들어, 브라우저가 다음과 같이 요청했다고 가정해봅니다:
Stream 1:
/index.html
(기본 요청)Stream 3:
/style.css
(렌더링에 필수 → 높은 가중치, 독립 요청)Stream 5:
/analytics.js
(낮은 우선순위)
우선순위 정보가 포함된 HEADERS 프레임은 다음과 같이 구성될 수 있습니다:
Stream 3 HEADERS: priority -> { dependency: 0, weight: 256 }
Stream 5 HEADERS: priority -> { dependency: 1, weight: 16 }
서버는 이 정보를 참고하여 중요한 스트림(예: /style.css
)의 프레임을 우선 전송하도록 대역폭을 분배할 수 있습니다.
Reflective Question:
만약 서버가 클라이언트가 보낸 우선순위 정보를 무시한다면, 이 기능은 어떤 의미를 가질 수 있을까요?
6. gRPC와 HTTP/2: 고성능 RPC를 위한 이상적 조합
gRPC는 HTTP/2의 멀티플렉싱, 스트리밍, 헤더 압축 등의 기능을 활용하여, 고성능 원격 프로시저 호출(RPC)을 가능하게 합니다.
gRPC 동작 예시
클라이언트 호출:
클라이언트는 gRPC 클라이언트 라이브러리를 통해 “함수 호출” 형식으로 서버의 메서드를 호출합니다.
이 호출은 내부적으로 HTTP/2의 새로운 Stream에서 Unary(단일 요청-응답) 또는 스트리밍 방식으로 전송됩니다.HTTP/2 프레임 교환:
클라이언트는 호출에 필요한 메타데이터와 페이로드를 포함한 HEADERS 및 DATA 프레임을 보냅니다.
서버는 해당 Stream에서 응답 HEADERS와 DATA 프레임을 통해 결과를 반환합니다.
[Stream 1: HEADERS] → gRPC 호출: "GetUserData" (메서드 이름, 메타데이터)
[Stream 1: DATA] → 요청 페이로드 (Protocol Buffers로 인코딩)
...
[Stream 1: HEADERS] → 응답 헤더 (상태, 메타데이터)
[Stream 1: DATA] → 응답 페이로드 (Protocol Buffers로 인코딩된 결과)
- 양방향 스트리밍 지원:
gRPC는 HTTP/2의 양방향 스트리밍 기능을 활용하여, 클라이언트와 서버가 동시에 데이터를 주고받을 수 있습니다.
이는 실시간 데이터 교환이나 채팅, 라이브 업데이트 등에 매우 유용합니다.
중요: 정식 gRPC는 반드시 HTTP/2 위에서 동작합니다. (브라우저 호환을 위한 gRPC-Web은 예외적입니다.)
Reflective Question:
gRPC가 HTTP/1.1이 아닌 HTTP/2를 선택한 이유는 무엇일까요?
어떤 HTTP/2 기능이 gRPC의 성능과 기능적 요구를 충족시킬 수 있었을까요?
결론
HTTP/2는 단순한 프로토콜 버전업 이상의 의미를 지닙니다.
멀티플렉싱, HPACK, 서버 푸시, 우선순위 기능 등을 통해 HTTP/1.1의 한계를 극복하고, 보다 효율적인 데이터 전송과 낮은 지연시간을 구현했습니다.
더 나아가, gRPC와 같은 최신 RPC 프레임워크는 이러한 HTTP/2의 강점을 활용하여 고성능 마이크로서비스 통신을 가능하게 합니다.
이 글을 통해 HTTP/2의 내부 동작과 실제 프로토콜 예시를 자세히 이해하셨다면, 이제 여러분은 단순한 사용자 수준을 넘어, 프로토콜 설계의 이면을 이해하는 개발자로 거듭날 수 있을 것입니다.
Subscribe to my newsletter
Read articles from 조정환 directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
