HOL (Head Of Line) 블로킹 문제

개요

기존의 HTTP의 요청은 큐 처리와 동일하다. 먼저 들어온 요청을 반드시 먼저 응답해주어야 하는 방식으로 동작하는데, 만약, 먼저 들어온 작업 요청에서 지연이 발생하면 이후 들어온 요청들은 먼저 들어온 작업이 끝날 때 까지 함께 대기해야 한다. 이 문제를 HTTP/2에서 어떻게 해결했는지 간단하게 정리한다.

HTTP/1.1

HTTP는 TCP 연결을 기반으로 동작하는 프로토콜이다. HTTP는 비연결성 프로토콜이기 때문에 한 번 연결로 한 번의 요청과 응답을 하고 응답이 끝나면 연결을 끊어버린다. 그런데 한 번 연결을 수립할 때 마다 오버헤드가 발생한다. 예를 들어, 전체 작업량을 10이라고 한다면, 연결을 수립하는데만 4의 작업량이 필요하다고 가정하면 한번의 요청과 응답만 하고 연결을 끊기에는 너무 아까울 것이다. 만약, 총 3번의 요청과 응답이 연속적으로 필요하다면, 한 번 연결을 수립한 후에 끊지 않고 3번의 요청과 응답을 수행하고 싶을 것이다. 그래서 HTTP/1.1에서 Keep-alive 기능이 추가되면서 한 번 맺은 연결을 끊지 않고 여러 번의 요청과 응답을 처리할 수 있게 되었다.

 

HTTP/1.1에서 성능 개선을 위해 파이프라이닝이라는 기술이 도입되었다. 하나의 커넥션에서 한 번에 순차적인 여러 요청을 하고, 그 순서에 맞춰 응답을 받는 방식으로 지연 시간을 줄이는 방법이다. 그러나, 큐 처리와 같이 순차적으로 응답을 받아야 하다보니 먼저 받은 요청에서 지연이 발생하면 그 뒤에 있는 요청의 처리가 아무리 빨리 끝나도 먼저 온 요청이 끝날 때 까지 기다려야 한다. 이를 HTTP의 HOL (Head Of Line) 블로킹 문제라고 한다. 현재 대부분의 브라우저에서는 파이프라이닝 기능을 막아놓았고, 대신 클라이언트가 동시에 여러 개의 커넥션을 수립한 후 각 커넥션 별로 하나의 요청과 응답을 처리하도록 하여 성능을 개선하고 있다. 즉, 큐를 없애고 각 요청 별로 커넥션을 수립하여 처리하는 방식을 사용하는데, 대신 앞서 언급했듯이 커넥션을 위한 오버헤드가 매우 클 것이다.

맥도날드에 가서 나는 더블 불고기 버거 1개를 주문하려고 했는데, 내 바로 앞에 있는 손님이 더블 불고기 버거 세트 30개를 주문한 경우, 그 사람의 더블 불고기 버거 세트 30개를 모두 만들어서 제공하기 전까지 나는 더블 불고기 버거를 못 먹게 되는 것과 같은 상황이다. (홀 (HOL)...)

어떤 지역에 맥도날드가 1km 간격으로 3개가 나란히 있다고 하자. 세 명의 손님이 각각 맥도날드 1호점, 2호점, 3호점에 가서 각자 원하는 메뉴를 주문하면 특정 손님이 더블 불고기 버거 세트 30개를 주문해도 다른 맥도날드에 영향을 미치지 않기 때문에 다른 손님들은 빠르게 원하는 메뉴를 먹을 수 있다.

HTTP/2

HTTP/2는 HTTP/1.1을 프로토콜의 성능에 초점에 맞추어 수정한 버전이라 생각하면 된다. 특히, 엔드 유저가 느끼는 지연, 네트워크, 서버 리소스 사용량 등과 같은 성능 위주로 개선됐다.

Multiplexed Streams

HTTP/2는 다중 스트림을 이용하여 커넥션 한 개로 동시에 여러 개의 요청과 응답을 주고 받을 수 있으며, 응답은 순서에 상관없이 스트림으로 주고 받는다. HTTP/1.1의 커넥션 Keep-alive, 파이프라이닝의 개선된 버전이라 보면 된다.

 

기존에는 큐 + 동기방식으로 처리했다면, 다중 스트림은 큐 + 비동기방식으로 처리한다고 보면 된다. 예를 들어, Node.js에서 파일 I/O 요청에 대한 큐가 있을 때, 기존에는 동기 방식으로 큐에서 하나씩 요청을 받아 파일 open/close를 수행했다면, 이제는 비동기 방식으로 요청 순서에 상관없이 파일 read가 완료된 순서대로 이벤트 큐에 담겨서 처리할 수 있도록 구현된 것과 같다. 내부적으로는 다중 스레드를 사용한 것과 같은 셈이다.

 

어떤 맥도날드에서는 손님 한 명의 주문을 한 명의 요리사가 전담하여 조리한다고 하자. 이 맥도날드에는 요리사가 4명이 있어서 최대 4명의 손님의 주문을 동시에 처리할 수 있다. 그리고 어떤 손님 한 명이 더블 불고기 버거 세트 30개를 주문하더라도, 해당 손님의 주문을 처리하는 요리사만 바쁠뿐이고, 나머지 3명의 요리사는 다른 손님들의 주문을 받아서 계속 조리를 할 수 있다. 따라서, 한 손님의 대량 주문이 다른 손님에게 미치는 영향이 줄어들게 된다.

Stream Prioritization

문서 내에 CSS 파일 1개와 이미지 파일 2개가 존재하고 이를 클라이언트가 요청하는 상황에서 이미지 파일보다 CSS 파일의 수신이 늦어진다면 브라우저 렌더링에 문제가 발생하게 된다. HTTP/2에서는 이러한 상황을 고려하여 리소스 간의 의존관계에 따른 우선순위를 설정하여 리소스 로드 문제를 해결한다.

맥도날드에서 불고기 버거 세트를 주문했는데, 콜라를 가장 먼저 따라 놓고 불고기 버거가 조리될 때 까지 기다린다면, 콜라의 시원함은 사라지고 미지근한 상태로 손님에게 제공될 것이고, 손님은 불고기 버거 세트에 만족하지 않을 것이다. 따라서, 불고기 버거와 감자 튀김의 조리가 완료된 후에 시원한 콜라를 따라서 제공해야 한다. 이와 같이 데이터를 제공할 때에도 우선순위를 고려하여 제공해야 한다.

Server Push

클라이언트가 서버로부터 웹페이지에 대한 기본 데이터만 요청하여 가져간 후에 서버가 요청하지 않은 관련 리소스들을 푸쉬하여 전달할 수 있다. 이렇게 푸쉬를 함으로써 클라이언트의 요청 횟수를 최소화할 수 있다.

 

맥도날드에서 손님이 불고기 버거 세트를 주문했는데, 마침 앞선 손님이 한 명도 없었고, 마침 불고기 버거 하나가 막 만들어진 상황이라 주문과 동시에 불고기 버거를 받아서 자리에 앉아 먹고 있었다. 잠시 후 감자튀김 조리가 완료되면서 종업원이 자리로 감자튀김을 가져다 주었고, 1분 후 콜라를 가져다 주었다. 손님은 한 번만 요청했지만 총 세번의 응답이 발생했다고 볼 수 있다.

Header Compression

HTTP/2는 헤더 정보를 압축하기 위해 Header Table과 Huffman Encoding 기법을 사용하여 처리하는데 이를 HPACK 압축방식이라 부른다.

 

클라이언트가 요청을 두 번 보낸다고 할 때, HTTP/1.x의 경우 헤더 중복이 발생해도 중복 전송하지만, HTTP/2 에서는 헤더에 중복이 있는 경우 중복되지 않은 헤더 정보를 Huffman Encoding 방식으로 인코딩한 데이터를 전송한다.

규모가 매우 큰 맥도날드에서 손님이 불고기 버거 세트를 주문하고 테이블 서빙을 요청하기 위해, 해당 위치를 종업원에게 힘들게 알려주었다. 그런데 나중에 손님이 메뉴를 추가하기 위해 다시 종업원에게 가서 본인이 받은 번호표를 알려주면서 이 번호표에 메뉴를 추가하겠다고 말하면 앉아있는 위치를 두 번 알려줄 필요가 없다.

참고

HTTP/2 알아보기 - 와탭

HTTP/1.1과 HTTP/2의 차이점 - seokbeomkim

'컴퓨터 공학 > Web' 카테고리의 다른 글

Hexagonal Architecture 소개  (0) 2022.07.02
주요 HTTP 상태 코드 (feat. 맥도날드)  (1) 2022.01.14