HTTP/1.0은 한 개의 요청과 응답마다 TCP 커넥션을 생성하여 사용했다.
이 방식은 매 요청마다 연결을 생성하는 오버헤드가 발생한다.
HTTP/1.1은 HTTP/1.0의 여러 불편함을 해결하는 것을 목표로 했다
대표적으로 HTTP/1.0 설계에서 미쳐 고려되지 못한 부분(계층적 프록시, 캐싱, 연결 지속)을 보완하고, HTTP/1.0을 통신한다고 선언해놓고 사양을 지키지 않는 서버와 클라이언트가 많다보니 이를 해결해야 했다.
HTTP/1.1은 이러한 문제를 연결 상태 유지(Persistent Connection) 이라는 지정한 타임아웃만큼 커넥션을 종료하지 않는 방식으로 해결한다. 기존 HTTP/1.0에서는 요청에 따른 응답이 수신되면 TCP연결을 바로 종료했다. 초창기의 웹이는 문제가 없었지만, 웹 페이지가 복잡해짐에 따라 문제가 되기 시작했다. 웹 페이지에서 다수의 HTTP 요청이 발생하면 매번 TCP 핸드셰이크 과정을 새로 거쳐야 했기 때문이다.
다만, 연결을 유지하는 시간이 길어질수록 서버에 부하가 생기기 때문에 연결을 유지하는 시간을 제한하고 있으며 이를 keep-alive라고 부른다.
파이프라이닝(Pipelining)도 지원해 요청의 응답 지연을 감소한다. 파이프라이닝에서 HTTP 요청은 연속적이며, 순차적으로 전달된다.
기존에는 요청한 이후에 응답을 기다리고 그 다음 요청을 보냈는데, 파이프라이닝에서는 필요한 모든 자원에 대한 요청을 순차적으로 서버로 전송한 다음 모든 요청에 대한 응답을 한 번에 기다리게 된다.
다만, 여전히 문제가 있었다. Head-of-Line Blocking(HOL Blocking) 문제로, 3개의 요청을 파이프라인을 통해 전송을 한다고 했을 때, 서버는 모든 요청을 순서에 맞춰서 응답해야 한다.
첫 번째 요청이 오래 걸린다고 하면, 나머지 요청은 첫 번째 요청의 처리를 기다려야 한다. 또한, 요청이 순차적으로 처리돼야 하기 때문에 서버에서 응답 작성 과정에 문제가 생기면 후속 요청들이 전송되지 못한다는 것도 문제였다.
HTTP/1.1에서 여러 한계점들이 드러나게 됐는데, 우선 헤더의 중복이 문제가 됐다.
HTTP/1.1에 여러 기능이 추가되면서 헤더에 많은 메타 데이터가 담기게 됐는데, 매 요청마다 헤더를 중복으로 전송해야 했다. 굉장한 낭비가 된 셈이다. 전송하려는 값보다 헤더의 크기가 더 큰 경우도 있었다.
HTTP/2.0은 헤더 필드 압축을 지원한다. 이를 HPACK라고 하는데 달라지는 부분만 다시 전송하는 허프만 코딩 기법을 사용한다. 달라지지 않은 부분은 전송하지 않기 때문에 불필요하게 발생하는 오버헤드를 최소화할수 있다.
HTTT/1.1는 메시지를 일반 텍스트 형식으로 전송했다. 텍스트 기반 프로토콜이라서 아스키코드로 작성됐고, 덕분에 사람들이 읽기는 편했지만 불필요하게 데이터가 커졌다.
2.0부터는 기존 HTTP 메시지를 프레임이라는 단위로 분할하고 이를 이진(binary) 형태로 만들어서 전송한다. 훨씬 효율적으로 데이터를 전송할 수 있게 된 것이다. 따라서, 기존 1.1 버전에 비해 파싱 및 전송 속도가 향상됐다.
또한, HTTP/2.0 부터는 멀티플렉싱(Multiplexing) 을 지원한다. 이는 하나의 커넥션을 사용하여 요청과 응답을 병렬로 처리할 수 있는 방식이다. HTTP/1.1은 TCP 연결에서 한번에 하나의 요청만 처리 가능하며 요청별 순서를 지켜야 했다. 이제 하나의 TCP연결에서 여러 요청을 동시에 처리할 수 있는데 이것은 TCPㅇ녀결을 스트림, 메시지, 프레임이라는 단위로 세분화했기 때문이다.
(클라이언트가 서버로 여러 요청을 동시에 보내도 각 요청이 독립적으로 처리되기 때문에 애플리케이션 레이어의 HOL Blocking 문제를 해결한다.)
HTTP/2.0은 여전히 TCP 위에서 동작하기 때문에 TCP로 발생하는 문제를 해결할 수는 업성ㅆ다.
우선, TCP는 신뢰성을 지향하기 때문에 데이터 손실이 발생하면 재전송을 수행한다.
그런데, TCP 패킷을 정확한 순서대로 처리해야 하기 때문에 재전송을 수행하고 대기하는 과정에서 병목현상이 발생했다.
TCP는 혼잡 제어를 수행하기 때문에 전송 속도를 낮은 상태에서 천천히 높이는 방식으로 속도를 제어한다. 네트워크 상황이 좋을 때는 불필요한 지연을 발생시킨다. 프로토콜 자체의 불필요한 헤더등도 고칠 수 없었다.
이를 해결하기 위해 HTTP/3.0은 QUIC라는 프토토콜 위에서 동작한다. TCP의 신뢰성 보장을 위해 제공되는 기능들을 UDP기반으로 직접 구현해 개선한, 구글이 2013년에 공개한 프로토콜이다.
HTTP/3.0은 연결 정보를 캐싱해서 재사용할 수 있는 O-RTT 기능을 제공한다. TCP의 경우 최초 연결 수립시 3-WAY 핸드 셰이크 과정이 필요하지만 HTTP/3는 최초 연결 설정에서 연결에 필요한 정보들과 데이터를 함께 전송해서 1-RTT로 시간을 절약한다. 한번 성공한 연결은 캐싱해놓았다고 다음 연결때에는 캐싱된 정보를 바탕으로 바로 연결을 수립하기 때문에 0-RTT가 가능하다.
또한, HTTP/3.0은 연결 다중화를 지원하며, 각 스트림이 독립적으로 동작한다. HTTP/2에서는 연결 다중화가 지원되며 여러 스트림을 동시에 지원할 수 있지만, TCP 특성상 데이터 손실이 발생하면 데이터 복구를 우선 처리하며 병목이 발생한다. 하지만, QUIC 기반의 HTTP/3은 연결 내 스트릠들이 완전히 독립적으로 동작하기 때문에 데이터 손실이 발생해도 다른 스트림에 영향을 주지 않는다.
HTTP/3은 IP기반이 아닌, 연결 별 고유 UUI를 이용해 각 연결을 식별한다. TCP기반 통신의 경우에는 WI-FI 환경에서 셀룰러 환경으로 이동하는 경우는 IP주소가 변경되기 때문에 연결 재수립 과정이 필요했다. QUIC은 연결 ID를 기반으로 식별하기 때문에 연결을 그대로 유지할 수 있다.
참고자료