ALB나 cloudfront를 이용하다 보면 HTTP/2를 지원하는 것을 확인할 수 있다.
HTTP/2는 HTTP/1.1에 비해 빠르다는 것 정도만 알고 있다. 이 두 버전을 비교해보며 HTTP/2에서는 정확히 어떤 것들이 개선되었는 지를 비교해본다.
HTTP/1.1
1997년에 릴리스된 프로토콜로 클라이언트와 웹 서버간의 정보를 교환하는 Layer 7의 프로토콜이다.
HTTP/1.1에서는 기존 1.0과 비교해 아래 2가지 특징을 가진다.
Persist Connection
기존 1.0 버전에서는 TCP 3way hand-shaking을 거쳐 HTTP Connection이 이루어졌다.
즉, 매 Connection을 생성할때마다 hand-shaking이 이루어지고, 큰 overhead로 작용되었다. (RTT 증가)
(Connection: keep-alive라는 헤더를 통해 Connection 재사용을 하는 경우도 존재)
HTTP/1.1에서는 Connection 헤더를 사용하지 않더라도, 모든 요청/응답에 기본적으로 Persistent Connection을 지원해 네트워크 지연을 줄일 수 있게되었다.
HTTP Pipelining
이전 버전인 HTTP/1.0은 Connection 하나당 하나의 요청을 처리하는 구조로 설계되어있다. 그렇기 때문에 요청과 응답이 순차적으로 진행된다.
응답이 오래걸리는 요청이 존재하는 경우, 뒤의 요청들이 계속 지연되는 문제가 발생되었다.
이러한 문제를 해결하기 위해 파이프라이닝이 등장하고 어느 정도 해결 되었다.
Pipelining은 하나의 Connection에 순차적인 여러 요청을 연속적으로 보내는 구조로 모든 요청에 대한 응답을 기다리지 않는다는 특징이 있다. (응답을 기다리지 않고 일단 요청을 전송)
이를 통해 네트워크 지연을 줄일 수 있었다.
하지만 파이프라이닝에도 여전히 문제가 존재했다.
서버는 반드시 응답을 요청 순서대로 맞추어서 전달해주어야한다는 전제조건(HTTP 프로토콜의 규칙)은 변함이 없기 때문에 특정 요청의 처리가 길어지는 경우, 다른 요청들도 처리가 지연된다.
파이프라이닝도 결국 응답처리를 뒤로 미루는 기술로, 이러한 전제조건에서 벗어날 수 없었다.
이를 Head of Line Blocking(이하 HOL)이라 하며 Pipelining의 대표적인 문제점이라 불린다.
또한 하나의 Connection에 하나의 요청만 처리하는 구조상의 이유로 매 요청별로 hand-shake가 발생해 불필요한 RTT가 증가해 네트워크 지연이 발생한다.
HTTP/2
위와 같은 HTTP/1.1의 문제점들의 개선과 전반적인 성능 증대에 초점을 맞춰 2015년, HTTP/2가 등장했다.
기존 HTTP/1.1에서 한 번의 Connection의 하나의 요청만 순서대로 처리하는 구조와 달리 HTTP/2에서는 각 요청들을 병렬로 전송해 Connection을 효율적으로 사용할 수 있게 되었으며, 클라이언트와 서버의 리소스 부담을 줄일 수 있게 되었다.
HTTP/2의 메시지 전송방식은 HTTP 메시지를 바이너리 형태의 프레임이라는 단위로 작게 분할한 뒤 전송하고, 송신 측에서 이를 다시 하나의 메시지 형태로 합친다는 것이다.
Multiflexed Stream
HTTP/2에서는 하나의 Connection에 여러 개의 스트림을 열도록 구성해 multiplexing을 구현했다.
한 쌍의 HTTP 요청과 응답은 하나의 스트림에 담기며, 하나의 Connection에 여러 쌍의 요청/응답이 보내질 수 있다.
이러한 Stream의 도입으로 병렬처리를 통해 Pipelining에서 발생했던 HOL문제를 해결할 수 있었다.
더불어, 스트림 간의 우선순위를 설정할 수 있다. (Prioritization)
Header Compression
HTTP 헤더에는 다양한 메타데이터가 존재한다. Stateless한 특징때문에 동일한 헤더를 여러번 전송하는 경우도 존재하는데, 이러한 메타데이터들이 HTTP 전송에 가장 큰 overhead를 유발한다.
또한, 이러한 헤더는 점점 커지는 추세로 HTTP의 무거운 헤더는 네트워크 지연의 주요 요소가 되었다.
HTTP/1.1의 무거운 헤더 문제를 해결하기 위해 HTTP/2에서는 HPACK 규격을 통해 헤더를 압축한다.
위와 같이 각 헤더의 개별 값 별로 압축 후 헤더 테이블에 내용을 저장한다
다음 요청에서 이전 요청에서의 동일한 헤더 내용을 발견하면, 이 헤더 테이블에 저장된 값을 색인한 뒤 대체해 클라이언트와 서버간의 대기시간을 줄일 수 있게된다.
Server Push
서버가 단일 클라이언트 요청에 대해 여러 응답을 보내는 방법으로, 하나의 요청에 대한 응답 이외에도 클라이언트가 명시적으로 따로 요청하지 않아도 다른 리소스를 클라이언트에 푸시하는 것이다.
아래 예시와 같이 클라이언트가 단일 페이지를 요청하면, 이 페이지에 참조된 다른 리소스(이미지, js,css 등)도 같이 응답(push)하는 것이다.
푸시되는 리소스는 클라이언트 측에서 거부하거나 푸시 스트림의 수를 제한할 수 있다.