HTML

웹사이트 속도를 높이는 방법

jeeyong 2007. 12. 21. 16:46
Minimize HTTP Requests
script 태그, link 태그, object 태그 , style 태그 , image 태그등과 같이 http 요청을 다시 유발하는 것들을 되도록 줄이라는 내용. 이런 페이지 구성요소들을 다시 불러오는 것이 응답시간의 80%를 점유 한다고 한다.
개선 방법으로 디자인를 간단히 하라는 것. (ㅡㅡ;)
그러나 RICH 한 UI 가 대세인 요즘. 아래의 방법이 속도를 빠르게 하는 팁이다.

  •  Image maps 의 활용 : 한줄로 정렬된 메뉴 버튼 같은 경우 통으로 된 하나의 이미지를 불러다가 쓰고 이미지맵을 통해 링크 처리한다. 이미지 사이즈는 비슷하며 대신 한번의 요청으로 불러오게 됨으로 http 요청수가 줄어든다. 단 , 열라 까다롭고 귀찮다.
  • CSS Sprites : 필요한 모든 이미지를 이미지 한장에 모두 담고 한번의 요청으로 가져온다.사용할때는 CSS 의 스타일로 적용해 통짜 이미지에서 필요한 부분만 보이게 해서 사용한다.이것도 상당히 귀찮다.그리고 CSS 크기가 증가한다.
  • Inline images : data: URL scheme 를 이용해 이미지 바이너리 표현을 페이지내에 하드코딩하는 것이다.(오 무식하다)
  • Combined files  : 스타일시트파일도 하나, 스트립트 파일도 통짜로 하나..뭐 이렇게 하는 것이다. 미국의 상위 10위안에 드는 사이트의 평균이 페이지당 스타일시트2개 스크립트파일 7개를 로딩한다고 한다.

여기서 의문점! 대부분 static 파일에 대한 이야기다. 대부분의 브라우저는 static 파일에 대해 cache 한다. 대부분의 파일이 캐쉬된다고 한다면 무시해도 되는 것이 아닐까?
캐쉬된 컨텐츠라해도 어차피 요청은 날라가고 304 응답이 떨어져 리턴되는 것마저 무시할 수 없는 것이라면 위의 방법을 고려해야 한다는 것이다.

Browser Cache Usage - Exposed! 를 보면 40-60% 의 방문자가 브라우저 캐쉬의 혜택을 보지 못한다고 한다. 따라서 static 파일이라고 해도 그 수를 줄이는 것이 방문자에게 체감속도를 높이는 방법이라고 한다.


2. CDN 을 사용하라.

유저가 보다 빨리 컨텐츠를 땡겨 갈 수 있게 유저와 가까운 곳에 서버를 두라는 것인데, 즉 여러 IDC 에 똑같은 일을 하는 서버들을 분산 배치 시키고 유저가 가장 가까운 서버와 통신하게 하는 방법.
자세한 것은 잘 모르겠으나, 대형 사이트들은 대부분 이방법을 사용한다고 한다.

 The Importance of Front-End Performance 에서 언급한 응답시간의 80-90% 가 페이지의 구성요소(스타일시트, 자바스크립트, 이미지,플래시 등)를 다운로드 하는데 소요된다는 것을 참고로 다운로드의 속도 향상을 위해 CDN 을 쓰라는 것이다.
야후는 20% 이상 개선효과를 봤다고 한다.

하지만 이것은 돈이 드는 것이고, 사이트의 유저가많거나 글로벌 서비스를 할때의 이야기인 듯.


3. Add an Expires Header


static 한 파일의 응답 해더에 Expires  해더를 셋팅하면 Expires 시간에 설정된 기간동안 브라우저 캐시한 컨텐츠를 사용하고 서버로 요청을 하지 않는다.
html,js,이미지파일 등에 대해 모두 적용가능하다.
단, 한번 설정된 파일은 지정한 시간까지 다시 서버에 요청을 하지 않으므로 업데이트를 해야 할 일이 있는 경우 파일의 이름을 변경해줘야 한다.
야후의 경우 js 파일 이름에 빌드넘버를 붙여 파일을 갱신하도록 한다고 한다.
(파일이름에 빌드넘버를 붙이는 이유를 잘 몰랐다가 이제야 알게됨..)

하지만 해더의 효과는 첫번째 방문하는 유저에게는 무의미하다.


4. Gzip Component

 Http 요청/응답 시간은 프론트엔드 개발자에 의해 크게 개선될 수 있다.
엔드유저의 네트웍 대역폭이나 인터넷 제공회선망 같은 것은 개발자가 어쩔수 있는 것은 아니다.
하지만, 실제 전송되는 데이타의 양을 줄임으로서 속도를 개선시킬 수는 있다.

Http1.1 에 추가된 해더인 Accept-Encoding 해더를 사용해 압축된 데이타를 전송할 수 있다.

Request 해더에 Accept-Encoding 해더를 붙여 보내면,

Accept-Encoding: gzip, deflate

서버는 나열된 압축방법 중에 하나로 컨텐츠를 엔코딩하고 Response 해더를 통해 클라이언트에 압축방식을 알려준다.

Content-Encoding: gzip

Gzip 이 가장 널리 쓰이는 방식이다.(RFC 1952.) Deflate 는 Gzip 에 비해 비효율적이고 널리 쓰이지도 않는다.

Gzip 은 Response 크기를 약 70프로 정도 줄여준다.(와우..) 그리고 현재 인터넷 트래픽의 90프로 정도가 이 기능을 제공하는 브라우저를 통해 이뤄지고 있다.
압축할때 서버자원이 소모되고 다시 클라이언트에서 압축을 풀때 클라이언트 자원이 소모되지만,
현재 인터넷 환경이 머신의 cpu 비용 보다는 네트웍 비용이 더 많이 든는 상태이므로(앞에서 언급했듯이) 시도해 볼 만한 것인듯 하다.
주의할 점은 이미지처럼 이미 압축된 컨텐츠에 대해서 gzip 처리를 하려고 해서는 안된다. 이미 이미지는 압축된 내용이므로 오히려 크기가 늘어난다. JSON 이나 XML 이 포함된 응답에서는 좋은 성능을 보인다.

본문에서는 아파치 사용시 예가있으므로 , iis6.0 사용시 gzip 사용법은 아래의 링크된 블로그를 참고해 보기 바란다.

IIS 6.0 + ASP.NET 에서 HTTP 압축 사용하기

HOW TO: Enable ASPX Compression in IIS



5: Put Stylesheets at the Top

스타일시트는 해드태그에 넣는 것이 로딩 타임을 줄여준다고 한다. 야후의 테스트 결과다.
해드테그에 모든 스타일시트가 걸려있으면 브라우저는 차례로 엘리먼트들을 그려나가지만,
스타일 시트가 아래에 배치된 경우 브라우저는 엘리먼트를 위에서 순차적으로 그린후 스타일에 따라
다시 그려야 한다. 따라서 브라우저는 마지막 스타일이 로딩되기까지 흰색으로 페이지 렌더링을 멈추고 있다가 스타일이 로드된 후 엘리먼트를 그린다. 파이어 폭스는 멈추지 않고 순차적으로 그린후 스타일시트에 따라 다시 그리는쪽으로 동작한다고 한다.
브라우저가 해드를 먼저 로딩하고 페이지 엘리먼트를 순차적으로 로드하는 쪽이 UX 상에도 좋다고 한다.

HTML specification  에서도 스타일 시트는 해드 태그에 넣으라고 명시하고 있다.


6: Put Scripts at the Bottom

스크립트의 경우는 반대다. 스트립트는 가능한한 페이지의 하단에 배치해야 한다.(외부 링크로 연결된 스크립트도 마찬가지)

첫번째 이유는 엘리먼트의 순차적인 렌더링을 위해서이고 두번째 이유는 병렬 다운로딩의 극대화이다.

엘리먼트 렌더링의 블록킹을 제거하고 순차적인 렌더링을 위해 스타일시트는 해드태그에 위치해 먼저 로딩되는 것이 좋지만, 왜냐하면 모든 스타일 시트가 로딩되기까지 페이지 렌더링이 블로킹 되기 때문이다.
스크립의 경우는 스크립의 아래에 존재하는 컨텐츠는 순차적인 렌더링이 블로킹된다.
따라서 가능한한 스크립트위로 컨텐츠를 배치시켜야 컨텐츠가 빠르게 표시된다.

또한, HTTP/1.1 specification 에서 한 호스트당 두개이하의 다운로드 커넥션을 권고하고 있다.
즉 한 호스트(hostname)당 동시에 두개의 컨텐츠만 다운로드 하라는 것이다.(대부분 브라우저가 이 규정을 지킨다고 한다.) 이미지 서버를 따로두고 호스트명을 다르게 한다면 보다 좋은 다운로드 속도를 낼 수 있다.
하지만 스크립트를 다운로딩 하는 동안에는 호스트명이 다르다 해도 새로운 다운로드를 시작할 수 없다.(왜 그럴까?)
모든 스크립트를 다 페이지 아래에 배치하지 못하는 경우도 있다. 이에 대한 대안으로 defer 속성을 셋팅하는 것이다. 이 속성은 페이지 렌더링후 스크립트를 처리하라는 표시인데 불행히도  파이어폭스에서는 동작하지 않는다고 한다.

AJAX.net 을 공부할때 생성되는 스크립트가 모두 페이지 하단에 생겨서 짜증냈던 적이 있는데, 다 그럴만한 이유가 있었군.

7: Avoid CSS Expressions

CSS expressions 은 IE 5 부터 제공되는 막강한 기능이데, IE 에서만 가능하고 클라이언트 자원을 너무 낭비한다.(너무 자주 호출된다)
가능하면 자바스크립트를 활용해 필요한 이벤트에서 한번씩만 처리되도록 하는 것이 좋겠다.



8: Make JavaScript and CSS External

자바스크립트나 스타일시트는 인라인코드(하드코딩)하는 것보다 외부파일로 빼내서 사용하는 것이 일반적으로 좋다. 그렇게 하면 브라우저 캐시를 사용할 수 있기 때문에 페이지마다 중복되는 것일 경우 보다 빠르게 페이지를 로딩하는데 도움이 된다.

하지만 무조건 외부파일로 빼는 것이 좋은 것만은 아니다. 유저가 자주 방문하는 페이지가 아니라면 그 효과가 그리 크지 않다.


9: Reduce DNS Lookups

DNS 는 hostname 을 ip 주소로 연결해주는 것인데, 브라우저창에 호스트명을 치는 경우 브라우저를 통해 DNS에 질의를 하고 DNS 가 리턴해주는 IP를  사용하게 된다. DNS 는 거치는 것도 엄밀히 Response time 에 영향을 준다.

DNS 질의 결과는 퍼포먼스 향상을 위해 캐시 될 수 있는데 , 특별한 서버를 두고 인트라넷 등 에서 처리할 수도 있지만 , 개인 pc 에 캐시할 수도 있다. 보통 OS 에서 DNS cache 기능을  제공하는데
윈도우즈의 경우 DNS Client service 가 그 역할을 한다. 대부분의 브라우저는 OS DNS 캐시와 별개의 자신만의 캐시를 따로 관리하는데, 이 캐시안에 목록이 있는 경우 OS DNS 캐시를 찾아보지 않고 바로 사용한다.

IE 의 경우 DNS 질의결과를 기본적으로 약 30분동안 캐시한다. 이것은 레지스트리에서 DnsCacheTimeout 값을 변경하여 수정할 수 있다.
Firefox 는 1분이 기본값이다. network.dnsCacheExpiration 값을 수정해 변경할 수 있다.

유닉크한 호스트명을 줄인다면 캐시가 저장되지 않은 상태에서(브라우저와 os 모두) 첫방문을 하는 하는 경우 페이내에 존재하는 컴포넌트(이미지나 스트립트..)를 불러오기 위해 DNS 질의를 하는 수를 줄일 수 있다. 그 만큼 페이지 로딩 속도가 빨라지는 것이다.
하지만 앞에서 언급한, 병렬 다운로드를 위해서는 호스명을 나눠서 처리하는 것이 좋다고 했는데 둘이 모순된다.
이 글에서는 그래서 2이상의 호스트명을 쓰되 4개 이상을 초과하지 않는 범위에서 페이지를 구성하는 것을 권고하고있다. 그래서 DNS 질의량을 적정선에 맞추고 병렬 다운로드로 이용하라고 한다.

10: Minify JavaScript

필요없는 문자들을 코드에서 제거해서 로딩속도를 높이란 말.






11: Avoid Redirects

리다이렉트(페이지 전환)은 301 이나 302 http 상태코드를 활용해서 이뤄진다.

   HTTP/1.1 301 Moved Permanently
      Location: http://example.com/newuri
      Content-Type: text/html

Response 해더를 위와 같이 셋팅해서 보내면 브라우저가 알아서 location 에 지정한 url 로 리다이렉션 한다.

즉 브라우저가 서버에 요청을 한번 하면 서버는 브라우저에게 바뀐 주소로 다시 요청하게 한다.
그럼 해더에 있는 다른 주소로 브라우저가 다시 요청하게 된다.
즉 하나의 페이지를 열기위해 두번의 요청이 발생하게 된다.

대부분의 개발자들이 아무생각없이 사용하는 '/'가 빠진 Url 의 경우 301 리다이렉션이 발생한다.
(오 이건 몰랐다..ㅜㅜ) http://test.test.com/test 로 요청하면 301 처리가 일어난후 http://test.test.com/test/ 로 재요청이 일어난다고 한다. 즉 '/' 를빼먹음으로 서 쓸데없는 Request가 한번 더 발생하게 되는 것이다.

보통 사이트를 개편하거나 했을 경우 즐겨찾기에 이전 url  를 링크한 유저들을 위해 redirect 를 사용해 새로운 페이지로 역결하는 서비스를 많이 하는데 , 이것보다는 url-rewriting 을 통해 서버상에서 바로 컨텐츠를 보여주는 것이 좋다고 권하고 있다.

12: Remove Duplicate Scripts

당연한 이야기다.
하지만 개발하다 보면 종종 발생하는 것이다.
페이지안에 부분들이 인클루드에 으해 처리되던 asp 시절엔 더 많았다.

이런것을 방지하려면 페이지 템플릿을 정해서 스크립트 라이브러리를 따로 잘 관리하는 방법이다.
개발자 개별로 스크립트를 짜게 하지말고 한 곳에서 라이브러리를 관리하고 페이지 템플릿에서는 반스시 이 라이브러리만 사용하도록 하는 것이다.


공백,탭,주석등을 없앤 코드를 실제 서비스에 사용하라는 말이다.(아마 이런기능이 울트라에디트에 있었던 것 같다)
자바스크립트에 대해 이런 처리를 해주는 툴이 JSMin 이란것이 있다고 한다.



13: Configure ETags

Entity tags (ETags)  는 서버상의 컴포넌트와 브라우저의 캐쉬에 있는 컴포넌트가 서로 일치하는지 알기위한 매카니즘이다.Entity 는 컴포넌트(이미지,스타일시트 등)를 말한다.
ETags 는 최종수정일로 판단하는 것보다 좀 더 유연한 식별방법을 제공한다.
Etags 는 유일하게 구별할 수 있는 버젼 스트링인데, 이것은 형식은 그냥 " 로 묶여있기만 하면된다.
그리고 서버측에서 Response 의 ETag 해더에 셋팅하여 클라이언트에 전송하면 된다.


      HTTP/1.1 200 OK
      Last-Modified: Tue, 12 Dec 2006 03:03:59 GMT
      ETag: "10c24bc-4ab-457e1c1f"
      Content-Length: 12195

클라이언트에서 이 컴포넌트 검증하려면(캐쉬된 것이 최신버젼인지..) If-None-Match 해더에 ETag 값을 넣어 질의 하면 된다. 만약 최신버젼이라면 304 만 리턴된다.
그러면 위의 경우 12195 바이트 만큼 네트웍크 대역폭을 절약할 수 있다.


      GET /i/yahoo.gif HTTP/1.1
      Host: us.yimg.com
      If-Modified-Since: Tue, 12 Dec 2006 03:03:59 GMT
      If-None-Match: "10c24bc-4ab-457e1c1f"
      HTTP/1.1 304 Not Modified

하지만, 웹서버 클러스터를 사용하는 경우 , 각 웹서버가 각기 다른 유니크 값을 만들어 ETag를 사용한다면 하나의 웹서버에서 내려준 Etag가 다른 클러스터 노드의 웹서버에서는 다른 것으로 간주 되는 경우가 발생하기 때문에 ETag 를 사용함으로 얻을 수 있는 이득이 감소되는 문제점이있다.

아파치나 IIS나 기본 제공되는 ETag 형식은 클러스터된 노드간 일치할 가능성이 전혀없는 형식으로 되어있다.

따라서 웹서버를 클러스트로 해서 사용하는 경우라면 차라리  ETag  를 해더에서 없애는 것이 좋다.
그리고 Last-Modifed 해더를 사용하는 것이 좋다.Microsoft Support article 에 ETag 를 없애는 법이 나와있다. 아파치에서는 구성파일에 아래의 내용을 셋팅하면 된다.


  FileETag none


14: Make Ajax Cacheable


를 이용해 AJAX 응답속도를 높일 수 있다.
이중 Rule3 이 가장 중요하다.

주소록이 하나 있다고 치자.
그 주소록의 최종 업데이트 일이 20071002(우선 년월일만 고려해보자) 라면, 이 주소록을 요청하는  AJAX URL 에 &date=20071002 라는 파라미터를 강제로 넣는 것이다. 그리고 그 결과에 대해 Expires 해더를 지정한다면  그 결과는 업데이트 날자가 변경될때 까지 브라우저에 캐쉬될 것이다.
만약 주소록이 업데이트 된다면 url 자체가 갱신 되기때문에 브라우저 캐시는 더이상 사용되지 않고 새로운 주소로 요청이 전송될 것이다. 그리고 다시 새로운 결과가 캐시될 것이다.
이렇게하면 AJAX 성능을 높일 수 있다.