해당 아티클을 읽고 요약한 내용입니다.
기술적인 관점에서 HTTP/1.1과 HTTP/2를 구분하는 가장 중요한 특징 중 하나는 인터넷 프로토콜 스택에서 애플리케이션 계층의 일부로 생각할 수 있는 바이너리 프레이밍 계층입니다.
모든 요청과 응답을 일반 텍스트 형식으로 유지하는 HTTP/1.1과 달리 HTTP/2는 바이너리 프레이밍 계층을 사용하여 모든 메시지를 바이너리 형식으로 캡슐화 합니다. 이때 메서드, 헤더 등 HTTP 시맨틱은 그대로 유지합니다.
일반 텍스트 형식의 HTTP 메시지
GET /index.html HTTP/1.1
Host: www.example.com
애플리케이션 수준의 API는 여전히 기존 HTTP 형식으로 메시지를 생성하지만, 이후 바이너리 프레이밍 계층에 내재된 멀티플렉싱의 도움으로 메시지를 바이너리로 변환합니다.
이렇게 하면 HTTP/2 이전에 만들어진 웹 애플리케이션이 새 프로토콜과 상호작용할 때 정상적으로 계속 작동할 수 있습니다.
Delivery Models
HTTP/1.1은 일반 텍스트 메시지로 전송하는 반면, HTTP/2는 이를 바이너리로 인코딩합니다.
HTTP/1.1
HTTP/1.0에서는 클라이언트가 새로운 요청을 할 때마다 TCP 연결을 끊고 다시 연결해야 했으므로 시간과 리소스 측면에서 비용이 많이 들었습니다.
HTTP/1.1에서 영구 연결(persistent connections)과 파이프라이닝을 도입하여 클라이언트는 각각의 응답을 기다릴 필요 없이 동일한 TCP 연결을 통해 여러 요청을 보낼 수 있어 성능이 크게 향상되었습니다.
그러나 여러 데이터 패킷이 동일한 목적지로 이동할 때 서로를 통과할 수 없기 때문에 필요한 리소스를 검색할 수 없는 대기열의 맨 앞에 있는 요청이 그 뒤에 있는 모든 요청을 차단하는 HOL(head-of-line) 블로킹이라는 병목 현상이 존재했습니다.
별도의 병렬 TCP 연결을 추가하면 이 문제를 완화할 수 있지만 클라이언트와 서버 간에 가능한 동시 TCP 연결 수에는 제한이 있으며 새 연결에는 각각 상당한 리소스가 필요합니다. 따라서 위 문제를 해결하기 위해 HTTP/2의 바이너리 프레이밍 계층이 제안되었습니다.
HTTP/2
바이너리 프레이밍 계층이 요청/응답을 인코딩하고 이를 더 작은 정보 패킷으로 잘라 데이터 전송의 유연성을 크게 높입니다.
HOL 블로킹 효과를 줄이기 위해 여러 개의 TCP 연결을 사용해야 하는 HTTP/1.1과 달리 HTTP/2는 두 시스템 간에 단일 연결 객체를 설정합니다. 이 연결 안에는 여러 개의 데이터 스트림이 있습니다. 각 스트림은 익숙한 요청/응답 형식의 여러 메시지로 구성됩니다. 각 메시지는 프레임이라는 작은 단위로 나뉩니다.
멀티플렉싱이라는 프로세스를 통해 그 뒤에 있는 메시지를 차단하지 않고 병렬로 실행할 수 있습니다. 따라서 다른 메시지가 완료될 때까지 기다릴 필요가 없도록 함으로써 HOL 블로킹 문제를 해결합니다.
바이너리 프레이밍 계층에 내재된 멀티플렉싱이 HOL 블로킹 문제를 해결하지만, 동일한 리소스를 기다리는 여러 스트림은 여전히 성능 문제를 일으킬 수 있다.
스트림 우선순위 지정은 동일한 리소스를 두고 요청이 경쟁하는 문제를 해결할 뿐 아니라 개발자가 요청의 상대적 가중치를 사용자 정의하여 애플리케이션 성능을 더 잘 최적화할 수 있게 해줍니다.
Buffer Overflow
두 컴퓨터 간의 모든 TCP 연결에서 클라이언트와 서버는 아직 처리되지 않은 수신 요청을 보관할 수 있는 일정량의 버퍼 공간을 확보하고 있다. 그런데 이 버퍼가 오버플로우 되어 일부 패킷이 손실될 수 있습니다.
버퍼 오버플로우를 방지하려면 흐름 제어 메커니즘을 통해 송신자가 수신자에게 데이터를 과도하게 전송하지 못하도록 해야 합니다.
HTTP/1.1
기본 TCP 연결에 의존합니다. 연결이 시작되면 클라이언트와 서버 모두 시스템 기본 설정을 사용하여 버퍼 크기를 설정합니다.
수신자는 ACK 패킷을 통해 현재 수신 윈도우 크기를 송신자에게 알립니다. 이를 바탕으로 데이터 전송 속도를 조절합니다. 기본 TCP 연결을 기반으로 수신 윈도우를 사용하면 연결의 양쪽 끝(소켓)에서만 흐름 제어를 구현할 수 있습니다.
버퍼 오버플로우를 방지하기 위해 전송 계층에 의존하기 때문에 각각의 새로운 TCP 연결에는 별도의 흐름 제어 메커니즘이 필요합니다.
수신 윈도우(Receive Window)
TCP(Transmission Control Protocol)에서 사용되는 중요한 개념입니다. 이는 네트워크 통신에서 데이터를 송수신할 때 데이터 흐름을 제어하기 위해 사용됩니다. 구체적으로, 수신 윈도우는 수신자가 한 번에 받을 수 있는 데이터의 최대 양을 나타냅니다. 이를 통해 송신자는 수신자의 버퍼 크기에 맞춰 데이터를 전송할 수 있습니다.
HTTP/2
바이너리 프레이밍 계층을 통해 단일 TCP 연결 내에서 데이터 스트림을 다중화 합니다. 그 결과 TCP 연결 수준의 수신 윈도우로 개별 스트림의 전송을 조절하기엔 충분하지 않습니다. 따라서 클라이언트와 서버가 전송 계층에 의존하지 않고 자체적으로 흐름 제어를 구현할 수 있도록 했습니다.
애플리케이션 계층은 사용 가능한 버퍼 공간을 전달하여 클라이언트와 서버가 멀티플렉싱된 스트림 수준에서 수신 윈도우를 설정할 수 있도록합니다. 이 세밀한 흐름 제어는 초기 연결 후 WINDOW_UPDATE 프레임을 통해 수정하거나 유지할 수 있습니다.
Predicting Resource Requests
클라이언트는 초기 GET 요청으로 HTML 파일을 응답으로 받은 후 페이지를 완전히 렌더링하기 위해 CSS, JS 파일 등의 리소스를 추가로 요청합니다. 이러한 추가 요청은 궁극적으로 연결 로드 시간을 증가시킵니다.
하지만, 서버는 클라이언트가 추가 파일이 필요하다는 것을 미리 알고 있기 때문에 클라이언트가 요청하기 전에 이러한 리소스를 전송하여 클라이언트의 시간을 절약할 수 있습니다.
HTTP/1.1
리소스 인라이닝을 사용하여 서버가 초기 GET 요청에 대한 응답으로 보내는 HTML 문서 내에 필요한 리소스를 함께 포함하여 클라이언트가 전송해야 하는 요청의 수를 줄일 수 있습니다.
그러나, HTML 문서 내에 포함시키는 파일이 크면 HTML 문서의 크기가 증가해 연결 속도를 저하시킬 수 있고 리소스가 HTML 문서와 분리되어 있지 않기 때문에 리소스를 거부하거나 리소스만 단독으로 캐시할 수 없습니다.
즉, 클라이언트가 리소스와 HTML 문서를 분리할 수 없다는 문제점이 있습니다.
HTTP/2
클라이언트의 초기 GET 요청에 대해 여러 개의 동시 응답이 가능하므로 서버는 요청된 HTML 문서와 함께 리소스를 제공할 수 있습니다.(HTML 문서에 포함하지 않고) 이 프로세스를 서버 푸시라고 합니다.
서버 푸시를 통해 클라이언트는 리소스를 별도로 캐시하거나 거부할 수 있습니다. 클라이언트는 서버 푸시의 우선순위를 조정하거나 비활성화할 수 있다.
Compression
웹 애플리케이션을 최적화하는 일반적인 방법은 압축 알고리즘을 사용하여 클라이언트와 서버 간에 전송되는 HTTP 메시지의 크기를 줄이는 것이다.
HTTP/1.1
gzip 압축은 CSS 및 JavaScript 파일의 크기를 줄이는 데 유용합니다. 그러나 HTTP 메시지의 헤더는 항상 일반 텍스트로 전송됩니다. 이에 따라 요청이 많아질수록 압축되지 않은 데이터의 부담으로 연결이 무거워집니다.
HTTP/2
헤더와 데이터를 분리하고 바이너리 프레이밍 계층을 통해 헤더 프레임과 데이터 프레임을 생성할 수 있습니다. 그러면 HTTP/2 전용 압축 프로그램인 HPACK이 헤더 프레임을 압축할 수 있습니다.