2019. 2. 26. 11:11ㆍLearning HTTP2
3.2 웹 성능 기법
2000년대 초, 야후에서 일하던 스티브 사우더스와 그의 팀은 클라이언트 웹 브라우저에 웹 페이지를 더 빠르게 불러오는 기법을 제시하고 그 효과를 측정했다. 그는 이 연구를 바탕으로 "웹 사이트 최적화 기법"(ITC, 2008)과 그 속편인 "초고속 웹사이트 구축"(위키북스, 2010)이라는 2권의 독창적인 책을 썼으며, 이 두 책은 웹 성능 기술 발전의 초석을 마련했다.
그 이후, 더 많은 연구를 통해 전환율, 사용자 참여율, 브랜드 인지도 측면에서 성능이 웹사이트 소유자의 수익에 직접적인 영향을 미친다는 사실이 확인되었다. 2010년, 구글은 검색 엔진에서 URL 순위를 산출하는 주요 파라미터로 성능을 추가했다. 대부분 사업에서 웹사이트를 보유하는 것이 계속 더 중요해지면서, 웹사이트 성능을 이해하고, 측정하고, 최적화하는 일도 중요해지고 있다.
이번 장 앞에서 논의한 바와 같이, 웹페이지 대부분의 경우, 브라우저 시간의 대부분은 호스팅 인프라에서 가져온 초기 콘텐츠(보통 HTML)를 표시하기보다는, 모든 콘텐츠를 가져와 클라이언트에서 페이지를 렌저링하는 데 소요된다. 이 사일이 [그림 3-5]의 도표에 나타나 있다.
그림3-5 프론트엔드 및 백엔드 시간 흐름도(출처 - http://stevesouders.com/images/golden-waterfall.png)
그 결과, 웹 개발자들은 클라이언트의 네트워크 지연을 줄이고 페이지 렌더링 시간을 최적화하는 방식으로 성능을 개선하는 데 점점 더 많은 관심을 기울이고 있다. 문자 그대로, 시간은 돈이다.
3.2.1. 웹 성능 모범 사례
앞서 언급한 것처럼, 웹은 상당히 많이 변해 왔으며, 최근 몇 년 동안 그 변화가 더 심했다. 비교적 최근의 모바일 디바이스의 보급, 자바스크립트 프레임워크의 발전, HTML 사용법의 진화를 고려해, 앞서 소개한 책들에 제시된 원칙을 다시 논의하고, 업계에서 사용하는 최신 최적화 시법을 알아둘 필요가 있다.
DNS 조회를 최적화하라
DNS 조회는 호스트와 연결이 수립되기 전에 이루어져야 하므로, 이 조회 절차는 가능한 빨라야 한다. 다음 모법 사례를 살펴보자.
- 고유한 도메인/호스트 이름의 수를 제한하라. 물론, 이는 항상 제어할 수 있는 것은 아니다. 하지만 HTTP/2로 넘어가면 고유한 호스트 이름 수의 상대적인 성능 영향도는 더 커질 것이다.
- 조회 지연 시간을 줄여라. DNS를 제동하는 인프라의 토폴리지를 이해하고 모든 최종 사용자의 위피에서 정기적으로 조회 소요 시간을 측정하라(모의 또는 실사용자 모니터링으로 가능하다). 서드파티 공급자를 사용하기로 결정했다면, 공급자마다 서비스 품질이 매우 다를 수 있으니 요구 조건에 가장 적합한 곳을 선택하라.
- 초기 HTML이나 응답에 애해 DNS 프리패차를 활용하라. 이는 초기 HTML을 내려받아 처리하는 동안 그 페이지에 있는 특정 호스트이름들의 DNS 조회를 시작할 것이다. 예를 들어, ajax.googleapis.com에 대한 조회를 미리 수행할 것이다.
|
이 기법들은 DNS의 고정적인 오버헤드를 최소화하는 데 도움을 줄 것이다.
TCP 연결을 최적화하라
이번 장 앞에서 논의한 것처럼, 새 연결을 여는 일은 시간이 오래 걸리는 절차일 수 있다. 연결이 TLS를 사용하는 경우라면(마땅히 그래야 한다), 그 오버헤드는 훨씬 더 크다. 이 오버헤드를 줄이는 방법은 다음과 같다.
- preconnect를 활용하라. 필요하기 전에 미리 연결을 수립해둠으로써 폭포수 임계 경로에서 연결 시간을 제거해준다. 예를 들면, 다음과 같다.
- 조기 종료를 사용하라. 콘텐츠 전송 네트워크를 활용하면, 요청하는 클라이언트와 가까이 위피한 인터넷 경계 에서 연결을 종료 시킬 수 있으며, 결국 새로운 연결을 수립할 때 수반되는 왕복 지연을 최소화할 수 있다. CDN을 더 자세히 알고 싶으면 7.5절 '콘텐츠 전송 네트워크'를 참조하라.
- HTTPS를 최적화하기 위해 최신 TLS 모범 사례를 실행하라.
|
많은 자원을 동일한 호스트에 요청하는 경우, 클라이언트 브라우저는 자원을 가져올 때의 병목을 피하려고 자동으로 서버와 병렬연결을 열 것이다. 현재 대부분의 클라이언트 브라우저는 6개 이상의 병렬연결을 지원하지만, 브라우저가 특정 호스트에 여는 병렬연결의 수를 사용자가 직접 제어할 수는 없다.
리다이렉션을 피하라
리다이렉션은 보통 다른 호스트로 연결을 하게 하며, 이는 추가적인 연결이 수립되어야 함을 의미한다. 무선 네트워크(휴대폰 등)에서 추가 리다이렉션은 수백 ms의 지연을 증가시킬 수 있다. 이는 사용자 경험에 나쁜 영향을 미치고 결국 웹사이트를 운영하는 기업에도 해가 된다. 특별한 상황을 제외하고는 대개는 리다이렉션을 할 만한 '타당한' 이유가 없다. 따라서 명백한 해결책은 리다이렉션을 완전히 제거하는 것이다. 쉡게 제거하기 어렵다면 다음 두 방법 중 하나를 선택할 수 있다.
- CDN을 활용하여 클라이언트 대신 '클라우드에서' 리다이렉션을 수행하라.
- 동일한 호스트 리다이렉션이라면, 리다이렉션을 사용하지 말고 웹 서버에서 'Rewrite Rules'를 사용하여 사용자를 원하는 자원으로 연결하라.
종종 리다이렉션은 검색 엔진 최적화라는 어둠의 마법을 도와, 단기 검색 결과 순위의 하락이나 그로 인한 백엔드 정보 레이아웃 변경을 피하는 데 사용된다. 이 경우, 리다이렉션의 대다다 SEO의 이득만큼의 가치가 있는지 판단해야 한다. 때로는 반창고를 한 번에 떼어버리는 것이 장기적으로는 최선책이다.
클라이언트에 캐싱하라
어떠한 네트워크 연결도 필요 없기 때문에, 로컬 캐시에서 데이터를 검색하는 것만큼 빠른 것은 없다. 누구나 알다시피(아닐 수도 있다). 가장 빠른 요청은 아무것도 요청하지 않는 것이다. 로콜에서 콘텐프를 검색하면, ISP나 CDN 공급자로부터 요금을 청구받을 일도 없다. 개체를 얼마나 오랫동안 캐싱할지는 브라우저에 알려주는 지시자가 바로 TTL이다. 주어진 자원에 대한 최적의 EEL은 과학적으로 완벽하게 찾을 수 없다. 하지만 다음과 같이, 검증된 몇 가지 지침이 좋은 시작점이 될 것이다.
- 이미지 또는 버전이 관리되는 콘텐츠와 같은 소위 정적 콘텐츠는 영구적으로 클라이언트에 캐싱할 수 있다. 하지만 TTL이 한 달이 넘을 정도로 오랜 시간 후에 만요되도록 설정되어 있더라도, 캐시 만료 시점 이전의 조기 퇴출이나 캐시 초기화 동작 때문에 클라이언트는 원본에서 정적 콘텐츠를 가져와야 할 수도 있다. 결국 실제 TTL은 디바이스 특성(특히, 캐시용 디스크의 가용량)과 최종 사용자의 브라우징 습관과 이력 정보에 달려있다.
- CSS/JS와 맞춤형 개체의 경우, 평균 세션 시간의 2배 정도 캐싱하라. 이 기간은 대부분의 사용자가 해당 웹 사이트를 돌아다니는 동안 로컬에서 자원을 가져올 수 있을 만큼 충분히 길며, 다음 세션에서는 네트워크에서 새로운 콘텐프를 가져오게 할 만큼 충분히 짧다.
- 그 외의 콘텐츠 유형인 경우, 이상적인 TTL은 주어진 자원에 대해 캐시에 유지하고자 하는 오후 임계값에 따라 다르므로, 요구 사항에 근거해 최적의 결정을 해야 할 것이다.
클라이언트 캐싱 TTL은 'Cache-Control' HTTP 헤더의 'max-age'(초) 키를 사용하거나 'Expires'헤더를 사용해 설정할 수 있다.
네트워크 경계에 캐싱하라
네트워크의 경계에 캐싱하면 모든 사용자가 클라우드 내의 공유 캐시의 혜택을 얻을 수 있으므로, 더 빠른 사용자 겸험을 제공하고 서비스 인프라에서 많은 양의 트래픽을 덜어 낼 수 있다.
캐싱할 수 있는 자원은 다음과 같다.
- 여러 사용자 간 공유 가능한 자원
- 어느 정도 노후도를 수용할 수 있는 자원
클라이언트 캐싱과 달리, 개인 정보(사용자 설정 정보, 금융 정보 등)는 동유할 수 없으므로 절대 네트워크 경계에 캐싱해서는 안 된다. 또한, 실시간 주식 거래 애플리케이션의 시세를 표시기처럼 시간에 매우 민감한 콘텐츠도 캐싱해서는 안 된다. 이 외에는 단 몇 초 또는 몇 분만을 위한 것이라도 무엇이든 캐싱할 수 있다. 예를 들어, 뉴스 속보처럼 그리 자주 변하지는 않지만 순간적으로 갱신해야 하는 콘텐츠인 경우, 모든 주요 CDN 업체가 제공하는 소거 메커니즘을 활용할 수 있다. 이를 'Hold Until Told'. 즉 '말하기 전까지 유지하기' 패턴이라고 한다.
조건부 캐싱
캐시 TTL이 만료되면, 클라이언트는 요청을 서버로 보낼 것이다. 하지만 대부분 그 응답은 캐싱된 사본과 동일할 것이고 이미 캐시에 있는 콘텐츠를 다시 내려받는 헛수고를 한 셈일 것이다. HTTP는 조건부 요청을 하는 기능을 제공하는데, 이는 클라이언트가 서버에 '그 개체가 변경되었으면 보내달라, 그렇지 않으면 그냥 동일하다고 알려달라'고 요청하는 기능이다. 어떤 개체가 자주 변경되지 않지만 그 개체의 최신 버전을 빨리 사용할 수 있게 하는 것이 중요한 경우에, 조건부 요청을 사용하면 대역폭과 성능 면에서 유리하다. 조건부 캐싱을 사용하는 방법은 다음과 같다.
- if-Modified-Since HTTP 헤더를 요청에 포함한다. 서버는 최신 콘텐츠가 헤더에 명시된 시점 이후에 갱신된 경우에만 전체 콘텐츠를 반환하며, 그렇지 않으면 응답 헤더에 새 타임스탬프 'Data'를 포함한 304 응답을 반환한다.
- 개체를 고유하게 식별하는 엔티티 태그, 즉 ETag를 요청 헤더의 ETag를 헤더에 포함하여 개체 자체와 함께 제공한다. 서버는 현대 ETag를 요청 헤더의 ETag와 비교한 후, 동일하면 304를 반환하고 다르면 전체 콘텐츠를 반환한다.
대부분의 웹 서버는 이미지와 css/js에 이 기법을 적용하지만, 다른 캐싱된 콘텐츠에도 적용하는지 확인해봐야 한다.
압축과 축소화
텍스트 형태의 모든 콘텐츠(HTML, JS, CSS, SVG, XML, JSON, 폰트 등)는 압축과 축소화의 혜택을 볼 수 있다. 이 두 방법을 함께 사용하면 개체의 크기를 대촉 줄일 수 있다. 더 적은 바이트 수는 더 적은 왕복을 의미하며, 이는 결국 시간도 덜 소요됨을 의미한다.
축소화는 텍스트 개체에서 불필요한 모든 요소를 제거하는 절차다. 일반적으로 이러한 개체들은 사람이 쉡게 읽고 관리할 수 있는 방식으로 만든 것이다. 하지만 브라우저는 가독성에는 아무런 관심이 없으며, 그 가독성을 포기하면 공간을 절약할 수 있다. 간단한 예로 다음 HTML을 보자.
<!DOCTYPE html> <html lang="ko"> <head> <meta charset="utf-8"> <title>앙큼한유채</title> </head> <body> <p>Hello, World!</p> </body> </html>
|
이는 하나의 완전한 HTML 페이지로, (볼품은 없지만) 브라우저에 정상 표시될 것이다. 그러나 여기에는 주석, 줄바꿈 문자, 공백 등 브라우저가 알 필요 없는 정보가 존재한다. 축소된 버전은 다음과 유사할 것이다.
|
이 버전은 읽기도 관리하기도 귑지 않지만, 바이트 수는 절반으로 줄었다
압축은 축소된 개체를 한 번 더 줄일 수 있다. 압축은 손실 없이 복원할 수 있는 알고리즘을 사용해 개체의 크기를 줄인다. 서버는 개체를 전송하기 전에 압축해 전송 바잍트 수를 90%까지 줄인다. 흔히 사용하는 압축 방식에는 gzip과 디플레이트가 있으며, 브로틀리처럼 비교적 최근에 등장한 방식도 있다.
CSS/JS 차단을 피하라
CSS는 화면에 콘텐츠를 렌더링하는 방법과 위치를 클라이언트 브라우저에 알려준다. 따라서 클라이언트는 화면에 첫 번째 픽셀을 그리기 전에 모든 CSS를 내려받아야 한다. 브라우저의 프리파서는 매우 지능적이어서 CSS를 어디에 두든 전체 HTML에서 필요한 모든 CSS를 미리 가져올 수 있다. 하지만 모든 CSS 자원 요청을 문서의 헤드 섹션 즉, JS나 이미지를 가져와 처리하기 전인 HTML 앞부분에 두는 것을 여전히 권장한다.
기본적으로 JS는 HTML 내의 코드가 위피한 지점에서 반입되어 파싱되고 실행되며, 브라우저가 이 작업을 완료할 때까지 해당 JS를 내려받아 실행하는 동안 HTML 나머지 부분의 파싱과 실행을 차단하는 것이 나을 수도 있다. 예를 들어, 소위 태그 관리자를 설치하는 경우 또는 존재하지 않는 엔티티에 대한 참조나 경쟁 조건을 피하기 위해 JS를 먼저 실행해야 하는 경우다.
하지만 이러한 기본적인 차단 동작은 대개 불필요한 지연을 발생시키고, 심지어 단일 장애점을 유발할 수도 있다. JS 차단의 잠재적인 역효과를 줄이려면 직접 제어할 수 있는 콘텐츠와 직접 제어하지 못하는 서드파티 콘텐츠에 각각 다른 전략을 수립하는 것이 좋다.
- JS의 사용 여부를 주기적으로 재확인하라. 시간이 지나면 웹 페이지가 더 이상 필요 없는 JS를 계속 내려받고 있을 가능성이 커지며, 이때는 필요 없는 JS를 제거하는 것이 가장 빠르면서도 효과적인 해결책이다!
- JS의 실행 순서는 중요하지 않되 onload 이벤트가 시작되기 전에 JS가 실행되어야만 한다면 다음처럼 'async' 속성을 설정하라
|
- JS의 실행 순서는 중요하되 DOM이 로딩된 후에 JS를 실행해도 된다면, 다음처럼 'defer' 속성을 사용하라.
- JS가 초기 화면 구성에 중요하지 않다면, onload 이벤트가 발생한 후에 JS를 가져와서 처리하는 것이 좋다.
- 메인 onload 이벤트를 지연시키고 싶지 않다면, 메인 페이지와 별개로 처리되는 iframe을 통해 JS를 가져오는 것을 고려할 수도 있다. 하지만 iframe을 통해 내려받은 JS는 메인 페이지의 요소에는 접근할 수 없다.
|
이 모든 것이 조금 복잡해 보인다면, 그것은 원래 그렇기 때문이다. 이 문제에는 일률적인 해결책이 없으며, 업무 요구 사항과 전체 HTML 문맥에 대한 이해 없이 득정 전략을 내세우는 것은 위험할 수 있다. 하지만 앞의 목록은 JS가 타당한 이유 없이 페이지의 렌더링을 중단시키지 않게 해주는 좋은 시작점이다.
이미지를 최적화하라
가장 인기 있는 많은 웹사이트에서 이미지의 상대적 및 절대적 크기는 시간이 갈수록 계속 증가하고 있다. [그림 3-6]의 차트는 지난 5년 동안 페이지당 요청 수와 전송 바이트 크기를 보여준다.
그림 3-6 2011~2016년의 전송 크기와 요청 수(출처 - httparchive.com)
현대 웹사이트들은 이미지로 가득 차 있기 때문에, 이미지를 최적화하면 성능상의 이득을 가장 많이 볼 수 있다. 이미지 최적화는 최소한의 바이트 수만으로 원하는 시각 품질을 달성하는 것을 목표로 한다. 이 목적을 달성하는 데 악영향을 미치는 요인들은 다음과 같은 방법으로 해결해야 한다.
- 피사체 위치, 타임스탬프, 이미지 크기, 해상도와 같은 이미지 '메타데이터'는 보통 이진 정보로 담겨 있으며, 클라이언트에 제공하기 전에 제거해야 한다(저작권과 ICC 프로파일 데이터는 제거하면 안 된다). 이 품질 무손실 절차는 생성 시점에 완료할 수 있다. PNG 이미지의 경우, 크기가 보통 10%가량 줄어든다. 이미지 최적화에 관해 더 많이 알고 싶다면, 팀 카들렉, 콜린 벤델, 마이크 맥콜, 요아브 바이스, 닉 도일, 가이 포쟈니가 쓴 "High Performance Images"(오라일리, 2016)을 읽어보라.
- 이미지 오버로딩이란, 이미지의 원본 크기가 브라우저의 창 크기를 넘거나 이미지 해상도가 디바이스 화면에 한계를 넘어, 브라우저에 의해 이미지의 크기가 자동으로 줄어드는 것을 말한다. 이렇게 브라우저에서 이미지가 축소되는 것은 대역촉뿐만 아니라 소중한 CPU 자원을 낭비하게 되며, 이는 보통 자원이 부족한 휴대용 디바이스에 더 큰 영향을 미친다. 이러한 영향은 반응형 웹 디자인 사이트에서 흔히 볼 수 있으며, 이들은 렌더링 디바이스에 관계없이 동일한 이미지를 제공한다. [그림 3-7]은 이러한 과도한 다운도르 문제를 보여준다.
그림 3-7 픽셀당 평균 RWD 바이트 수(출처 - https://www.slideshare.net/AkamaiTechnologies/chicago-tech-day-jan-2015-rwd)
3.2.2. 안티패턴
HTTP/2는 호스트마다 하나의 연결만 열기 때문에, HTTP/1.1의 모범 사례 중 일부는 h2에서는 안티패턴으로 바뀐다. 다음 절에서는 h2 웹사이트에는 더 이상 적용되지 않는 인기 있는 방법 몇가지를 논의한다.
스프라이팅과 자원 통합/인라이닝
스프라이팅은 작은 이미지 여러 개를 큰 이미지 하나로 통합하여 여러 개의 이미지 요소를 단 한 번만 요청할 수 있게 하는 것을 말한다. 예를 들어, 색상 견본이나 탐색 요소(화살표, 아이콘 등)들은 스프라이트라고 부르는 하나의 큰 이미지로 모을 수 있다. 더 이상 특정 요청으로 인해 무언가가 중단되는 일 없이 많은 요청을 병렬로 처리할 수 있는 HTTP/2에서, 스프라이팅은 성능적인 면에서 더 이상 고려할 가치가 없다. 또한, 웹사이트 관리자는 더 이상 스프라이트를 만들 걱정을 하지 않아도 된다. 그렇다고 해서 기존 스프라이트까지 원래대로 분리하는 수고를 할 필요는 없다.
같은 맥락으로, 클라이언트와 서버 간의 연결 수를 줄이기 위해 JS와 CSS같은 소규모 텍스트 자원들도 더 큰 단일 자원으로 통합하거나 메인 HTML에 직접 포함할 수 있다. 한 가지 역효과로, 독립적으로 캐싱할 수 있는 CSS나 JS를 캐싱할 수 없는 HTML에 포함하면 근복적으로 캐싱할 수 없게 되어 버리므로, 사이트를 h1에서 h2로 마이그레이션하는 경우에는 이 방법을 피해야 한다. 하지만 2015년 11월, khanacademy.org에서 발표한 연구에 따르면, 많은 소규모 JS파일들을 하나의 파일로 묶는 것이 압축과 CPU 자원 절약이라는 면에서 h2에서도 적절할 수 있다.
샤딩
샤딩은 호스트 이름마다 다수의 연결을 열어 콘텐프를 병렬로 내려받는 브라우저의 기능을 활용하는 것이다. 특정 웹사이트에 대한 최적의 샤드의 수는 과학적으로 정확하게 찾을 수 없으며, 업계에 여러 견해가 존재한다고만 말해두는 편이 적절하다.
HTTP/2에서는, 샤딩을 해제하려면 사이트 관리자가 상당히 많은 작업을 해야 한다. 더 나은 방법은 기존 샤딩을 유지하면서 동일한 서버 IP와 포트에 매핑된 다수의 호스트이름이 공통 인증서(와일드카드/SAN)를 공유하게 하여, 네트워크 통합의 이득을 얻고 샤딩된 호스트이름 각각에 대한 연결 수립을 줄이는 것이다.
쿠키 없는 도메인
HTTP/1에서는 요청과 응답 헤더의 내용은 압축되지 않는다. 시간이 갈수록 헤더의 크기가 커지고 있기 때문에, TCP 패킷 한(~1.5KB)보다 쿠키 크기가 더 큰 경우는 더 이상 이상한 일이 아니다. 결과적으로, 서버와 클라이언트 사이에 헤더 정보가 오가는 동안 무시할 수 없을 만큼의 지연이 발생할 수 있다.
따라서 이미지처럼 쿠키에 의존하지 않는 자원을 위해 쿠키 없는 도메인을 만드는 것이 합리적인 권고 사항이었다.
하지만 HTTP/2에서는, 헤더를 압축할 수 있을뿐더러 이리 말려진 정보를 전송하지 않기 위해 양 단에 '헤더 이력'을 저장한다. 따라서 HTTP/2로 사이트를 재설계하면 쿠키 없는 도메인을 만들지 ㅇ낳아도 되므로 삶이 더 편해진다.
다수의 정적 개체를 동일한 호스트이름에서 HTML로 제공하면 그 정적 자원들을 가져오는 것을 지연시키는 추가적인 dns 조회와 소켓 연결을 피할 수 있다. 렌더링을 중단시킬 수 있는 자원을 동일한 호스트이름에서 HTML로 전송하면 성능을 개선할 수 있다.
3.3. 요약
HTTP/1.1은 성능 최적화와 모범 사례라는 흥미롭지만은 않은 복잡한 세계를 만들어 냈다. 성능을 짜내려는 업계의 온갖 시도는 찬사를 받을 만했다. HTTP/2의 목표 중 하나는 이러한 많은(전부는 아닐지라도) 기법들을 더 이상 쓸모없게 만드는 것이다. 그럼에도 이 기법들과 동작 원리를 이해하면 웹과 웹의 동작 방식을 더 싶게 이해할 수 있을 것이다.
'Learning HTTP2' 카테고리의 다른 글
CHAPTER5 - HTTP/2 프로토콜-1 (0) | 2019.02.28 |
---|---|
CHAPTER4 - HTTP/2로의 전환 (0) | 2019.02.26 |
CHAPTER3 - 웹을 파헤치는 이유와 방법 -1 (0) | 2019.02.25 |
CHAPTER2 - HTTP/2 맛보기 (0) | 2019.02.25 |
CHAPTER1 - HTTP의 진화 (0) | 2019.02.25 |