본문 바로가기

소프트웨어-이야기/인프라

[HTTP] HTTP 2의 탄생 배경과 특징

팀에서 HTTP 2에 대한 내용을 간단하게 ㅎㅎ 발표하기로 했다. 그래서 준비중이다.

그런데 피피티로 쓰는 것 보다 글로 정리하는게 더 나을것 같아서, 에버노트 대신 블로그에 포스팅을 하려고 한다.

( 결국 이 포스팅을 읽으면서 발표했다 ㅎㅎ )


A.

https://www.httpvshttps.com/

이 사이트에 들어가면, HTTP 1과 HTTP2의 웹페이지 랜딩 속도를 비교해볼 수 있다.

보면, HTTP2가 HTTP1보다 절반은 빠르다. 

HTTP2는 이전 버전에 비해, 속도 측면에서 큰 변화가 있었다. 

HTTP2는 보안 강화라는 특징도 갖고 있지만, 이번 시간에는 HTTP2가 웹페이지를 요청하고, 응답받을 때까지 기다려야하는 시간을 어떻게 단축했는지에 대한 내용에 집중에서 이야기하려고 한다. 


B.


HTTP 2는 2015년 05월에 공개되었다. HTTP 1.1이 발표된 시점은 1997년도였다.

HTTP2으로 버전업이 될 때까지 16년이라는 시간이 걸렸다.


그 사이에, 웹이 처리되는 방식이 많이 바뀌었다.


1. 필요한 리소스의 양이 달라졌다.


2001년도의 야후 웹페이지이다

1996년도의 야후 웹페이지 리소스는 34KB였다고 한다.

그런데 2017년도 기준으로 봤을 때, 웹페이지를 로딩할 때 받아야하는 리소스의 크기가 평균 2300KB라고 한다.

필요한 리소스의 크기가 이전에 비해 60배가 넘는다.


2. 전에 비해 웹페이지가 동적으로 작동된다.


C. 

HTTP1이 느린 이유는 여러가지가 있다.


C-1.

HTTP 1에서는 앞에서 날렸던 요청의 응답을 받아야만 다음 요청이 처리될 수 있다. 


HTTP 1.0 버전대만 하더라도, Request를 날릴 때마다 Connection을 새로 생성했다.

그런데 이건 비효율적이여서, HTTP1.1에서는 지속 커넥션(Persistent connection)이라는 개념과 HTTP 파이프라이닝(Pipelining)이라는 개념이 들어갔다. 커넥션을 재사용할 수 있고, Request를 미리 여러개 서버에 날릴 수도 있게 되었다.

그런데 이러한 개선에도 불구하고, Request를 보낸 순서대로 Response를 받을 수 있다는 지점에서 문제가 발생했다. (참고)


참고


만약, 처음에 요청한 Request에 문제가 있어서, 응답이 늦어지면

2번째, 3번째에 요청한 Request의 응답도 같이 늦어진다는 문제점도 발생한다. ( Head Of Line Blocking )



D.

프로그래머들은 이러한 한계 속에서 기능 개발을 해왔다. 

그리고 이런 상황을 개선하기 위해, 여러 해결책들을 찾아서 사용해왔다.


E.

HTTP1 환경에서 웹 브라우저에 웹 페이지를 보여줄 때, 

한 Connection에서  Request를 보내고 Response를 받기를 기다리고, 다시 또 이를 순차적으로 반복하는건 시간이 오래걸린다. 

C-1, C-2 같은 상황이 발생하는거다.

그래서 프로그래머들이 사용한 최적화 방법 중 하나는 도메인 샤딩이다.


서버는 같지만, 도메인명을 여러개 설정해서 이를 리소스 주소로 내려주면,

한 브라우저에서 여러개의 커넥션을 맺을 수 있게 된다. 

이렇게 되면, 리소스를 병렬적으로, 동시다발로 받을 수 있게 된다. 


그런데.. 브라우저별로 도메인 커넥션 갯수 제한이 있어서... 근본 해결책은 되지 못했다고 한다... 

그리고 너무 많은 도메인을 연결하는 경우, DNS 검색과 TCP Handshake에서 발생하는 시간 때문에 역효과가 날 수도 있다고 한다.. 


F.

HTTP2에서는 Multiplexing 개념이 도입되었다. 그래서 더이상 도메인 샤딩 같은 제 2의 방법들이 필요하지 않게 되었다.

HTTP 1.1에서는 한 Reuqest 당, 한 개의 리소스를 받아올 수 있었다. ( 한 Connection에서 한번에 하나의 Response를 받을 수 있다 )

그런데 HTTP2에서는 동시에 여러 리소스를 받아올 수 있게 되었다. ( 참고 )

한 커넥션 안에서 여러 리소스를 동시에 받을 수 있게된 배경 기술은 아래와 같다.


Binary Protocol


기존의 HTTP1은 플레인텍스트로 구성되어 있었다. 

그런데 HTTP2에서는 데이터를 전송할 때, 바이너리로 인코딩하여 전송하게 되었다.


바이너리 포맷의 데이터를 사용하게 되어, 이전에는 한 뭉태기로 모여있던 데이터를 프레임이라는 단위로 나눠서 관리 / 전송할 수 있게되었다.


HTTP 1.1에서 HTTP 요청과 응답은 메시지라는 단위로 구성되어 있다. 메시지는 Header / Body 등의 데이터로 구성되어 있다. 

그런데 HTTP2에서는 Frame과 Stream이라는 개념이 추가되었다. 

Frame은 HTTP2 통신에서 데이터를 주고받을 수 있는 가장 작은 단위이다. 헤더 프레임, 데이터 프레임으로 구성되어 있다.

메시지는 HTTP1.1처럼 요청과 응답의 단위이다. 메시지는 다수의 Frame으로 구성되어 있다.

스트림은 클라이언트와 서버사이에 맺어진 연결을 통해 양방향으로 데이터를 주고받는 한개 이상의 메시지를 의미한다.


즉, 프레임이 여러개가 모여, 메시지가 되어, 메시지가 여러개가 모여, 스트림이 되는 구조이다 


Frame > Message > Stream


HTTP 1 시절에는, 요청과 응답이 메시지라는 단위로 완벽하게 구분되어있었다. 그런데 HTTP2에서는 스트림이라는 단위로 요청과 응답이 묶일 수 있는 구조가 만들어졌다.


HTTP2에서는 스트림 하나가 다수개의 요청과 응답을 처리할 수 있는 구조로 바뀌었다. 

동시에 여러 메시지를 처리할 수 있게 되었다. 그래서 응답 프레임들은 요청 순서에 상관없이 만들어진 순서대로 클라이언트에 전달될 수 있게 되었다. 

그래서 HTTP1 때처럼 중간에 응답이 막히면,  대기하고 있던 Response들이 모두 기다려야하는 HeadOfBlocking 이슈에서 벗어날 수 있게 되었다.

( 스트림에 우선순위를 설정할 수 도 있다고 한다 )


G. 

다시 아까 했던 이야기를 이어서 해보려고 한다. 

위에서 언급했던 도메인샤딩은 HTTP1에서 병렬적으로 동시에 데이터를 처리하려고 했던 노력에 대한 이야기였다. 

이번에는 HTTP1에서 성능 향상을 위해 노력한 다른 방법에 대해서 이야기하고자 한다.


기본적으로 HTTP1은 한 Request으로 한 리소스를 받아올 수 있기 때문에 느리다.


그리고, 브라우저에서 한 html 리소스를 받는게 완료되어야, 다음 리소스 ( js, css, image)를 받을 수 있다는 구조 때문에 느리기도 하다.

위의 이미지 같은 상황에서 HTTP1이 느리다고 느껴진 이유는

서버에서 HTML 태그를 받아서, 파싱하고 -> 다시 필요한 리소스들을 순차적으로 요청해서 받아오기까지를 기다려야한다는 점 때문이다.

http://www.businessinsider.com/cloudflare-announces-http2-server-push-2016-4


그래서 속도를 개선하기 위한 관점 중 하나는 요청수 자체를 줄이는 게 있었다. 

요청수를 줄이기 위한 최적화 방법은 아래와 같다.


1. 이미지 요청수 최적화 - Image Splite CSS

큰 통짜 이미지를 한 번에 받아와서 CSS으로 이미지를 쪼개서 웹페이지에 보여주는 방법

( 실서비스에서 자주 사용되는 방법이다. )


2. 이미지 요청수 최적화 - Data URI Scheme

이미지를 BASE 64인코딩을 사용해서, 이미지를 코드로 보여주는 방식


3. Javasacript / CSS 리소스 요청수 최적화 - 파일 압축

레일즈 Production 환경에서는, JavaScript와 CSS가 각각 한 파일에 자동으로 압축 (Concatenation)되서 사용된다. ( rails asset_pipeline )

왜냐면 파일이 여러개 있으면, 

리소스를 받기위해 하나하나 connection을 맺어서, 다운로드를 받아야하기 때문이다.

이렇게 하나로 합쳐두면, JavaScript / CSS  데이터를 각각 한번만 내려받으면 된다.


but....


HTTP 2에서는 이러한 노력이 구우우우우우욷이 필요하지 않다.

Server Push가 있기 때문이다.


H.

'Server Push'라는 개념을 처음 봤을 때, 웹소캣 같은 느낌인건가?? 싶었다. 엄청 획기적인데? 싶었다.

혹시 이런게 Server Push..? @_@


그런데 찾아보니 이런 느낌은 아니였다.


이런 느낌이다. ㅎㅅㅎ


원래 웹 브라우저가 웹페이지를 보여주는 절차는 아래와 같다.

- 애플리케이션에서 HTML 태그 데이터를 내려준다 

- 브라우저는 태그를 파싱해서, 또 요청을 날려야하는 리소스들이 뭔지 파악한다

- CSS / 자바스크립트 / 이미지 등등을 요청해서 받아온다.

- 브라우저에 그려준다.


A-1의 이미지처럼, 웹 브라우저에서 리소스를 보려면 계속 기다려야한다는거다.

그런데 사실, 애플리케이션은 얘가 어떤 데이터를 받아봐야하는지 알고 있다.


브라우저에서 필요한 리소스들이 이미 서버에 있는 로직들이라면,

웹브라우저가 다시 Connection을 맺고, Request를 날리기까지 기다리는건 비효율적이다.


그래서 이런 것들을 개선하기 위해 Server Push가 등장했다.

ServerPush란, 브라우저에서 필요한 리소스들을 서버가 알아서 찾아다가 내려주는걸 의미한다고 보면 된다. 

그래서 이제는, 

HTML 태그에서 필요한 리소스가 뭔지 찾기 전에, HTML 태그를 받은 즉시, 리소스(CSS, JS, IMAGE)를 페이지에 그릴 수 있게 되었다.


작동 원리는 완전하게 이해하기 어려워서, 내용 설명을 생략한다 ㅎㅎ 

PUSH_PROMISE frame을 보내서, 응답값을 예약한 것 같은데... ( 정확한 의미를 이해하지 못해서, 나도 내가 무슨말을 하는건지 모르겠다 )


사용방법은 Link라는 Header에 push할 리소스를 추가해주면 된다.

Link: </css/style.css>; rel=preload;


인터넷에서 찾아보니, 이런식으로  server push용 header를 셋팅해주는 형식으로 레일즈에 코드를 추가한 사람도 있었다. ( 참고 )

SERVER PUSH 기능이 항상 좋은 것만은 아니다.

* 캐싱되지 않은 리소스를 받아올 때, 

* 페이지에서 필요한 리소스가 페이지를 내려주는 서버에 있을 때,

유용하다. 


만약 캐싱되어서 재사용되는 리소스를 PUSH 하거나, 

CDN 처럼 다른 서버에서 내려주는 리소스 때문에 Blocking이 걸리는 경우에 PUSH를 하는 건 

성능 향상이라는 본질적인 목표를 해결해주지 못한다. 참고


I

그 외... http2 속도 향상에는 헤더 압축으로 인한 성능 향상도 있었다.

HTTP1 시대에서는 HEADER가 점점 뚱뚱해져서, Request / Response HEADER가 비대해졌다. 

주고 받아야하는 데이터 중, 디폴트로 딸려오는 헤더 데이터들이 무척 커졌다는거다.


그래서 HTTP2에서는 HEADER를 압축해서 관리한다



* HTTP2는 HEADER를  Header Table으로 관리한다. 이전 리퀘스트에서 중복으로 선언된 헤더는 인덱스값만 전송해서, 

  전송해야하는 데이터 양을 최소화한다.

* 이 중, 새롭게 추가되거나 변경된 헤더는 Huffman 인코딩이 되어 전송된다.


HTTP2에서 이 두가지를 적용한 방법을 HPACK 압축 방식이라고 한다.




QnA

레일즈에서 HTTP2를 사용하려면 어떻게 쓰면 되나요? 

*나도 모르겠다! 스택오버플로우를 뒤져보니.. HTTP2를 지원하는 웹서버를 사용하라고 한다. 대신, 서버푸시 같은 기능은 사용할 수 없다고 한다.

  그런데, 엔진엑스 자료를 보니 2015년도에 SPDY로 구성된 서버 중 90% 이상이 엔진엑스로 되어있다고 하던데.. 다른데서는 어떻게 지원하고 있는건지 궁금하다. 

* 루비에서 사용할 수 있는 HTTP2 관련 GEM

ds9     : https://rubygems.org/gems/ds9

http-2 : https://github.com/igrigorik/http-2


HTTP2을 사용할 수 있도록, 셋팅하려면 어떻게하면 되나요?

* HTTP2는 TLS 기반에서 작동되기 때문에, TSL / SSH 인증서가 필요하다. 인증서를 받아서 설정하고, HTTPS에서도 웹페이지가 동작되도록 수정해야한다.

* 그리고 HTTP2를 지원하는 네트워크 인프라 셋팅이 필요하다. 엔진엑스는 HTTP2를 기본으로 지원하고 있다. 아파치도 HTTP2 모듈을 지원하고 있다. 참고: 아파치/엔진엑스 HTTP2 설정 방법

* 그리고 테스트를 하면 된다. 




참고한 자료들 묶음

외국 자료

HTTP 히스토리 : https://kinsta.com/learn/what-is-http2/

SPDY 피피티 자료 : https://libosong.appspot.com/spdy/index.html#16

How NGINX Plans to Support HTTP/2: https://www.nginx.com/blog/how-nginx-plans-to-support-http2/

CDN 업체인 CloudFlare에서 작성한 HTTP2 서버 푸시에 대한 자료 (1) : https://blog.cloudflare.com/announcing-support-for-http-2-server-push-2/

CDN 업체인 CloudFlare에서 작성한 HTTP2 서버 푸시에 대한 자료 (2) : https://www.cloudflare.com/website-optimization/http2/serverpush/

루비와 HTTP2에 대해서 발표한 일본인 영상 : https://www.youtube.com/watch?v=_KFxWyJrzso

오를리 사 책 중 스트림과 프레임에 대한 내용 - streams-messages-and-frames
바이너리프로토콜 - binary-framing-layer
레일즈에서 서버푸시를 사용한 케이스의 스크립트 - https://gist.github.com/tomfuertes/6978b594d34038d763a9ee3c4d4c9b14
서버푸시가 잘못 사용되고 있다는 내용의 포스팅 - https://www.daveyshafik.com/archives/69603-http2-server-push-youre-doing-it-all-wrong.html


한글 자료

Spriting 이미지, 왜 만들게 되었을까? (HTTP 1.1 커뮤니케이션 구http://www.popit.kr/%EB%82%98%EB%A7%8C-%EB%AA%A8%EB%A5%B4%EA%B3%A0-%EC%9E%88%EB%8D%98-http2/조의 이해) : http://nuli.navercorp.com/sharing/blog/post/1132449

SPDY by Google, Speed+Mobility by Microsoft and HTTP 2.0 : http://nuli.navercorp.com/sharing/blog/post/1132452

MDN의 HTTP/1.x의 커넥션 관리 포스팅 : https://developer.mozilla.org/ko/docs/Web/HTTP/Connection_management_in_HTTP_1.x

POPIT - 나만 모르고 있던 HTTP2 : http://www.popit.kr/%EB%82%98%EB%A7%8C-%EB%AA%A8%EB%A5%B4%EA%B3%A0-%EC%9E%88%EB%8D%98-http2/

HTTP2 스트림와 프레임 브랜치 : https://brunch.co.kr/@sangjinkang/3

HTTP2 전체적인 동작 방식 : https://b.luavis.kr/http2/http2-overall-operation



더 읽어보면 좋을 자료

인터넷을 빠르게 하는 방법 (비즈니스인사이드) - http://www.businessinsider.com/the-internet-is-about-to-get-faster--heres-why-2015-2

HTTP 2에 대한 외국 자료 - https://devopedia.org/http-2