본문 바로가기

프로그래밍(TA, AA)/자바스크립트

[자바스크립트] 기본적인 웹 사이트 최적화 방법

HTTP 요청 최소화


HTTP 요청 최소화는 최적화에서 가장 기본적이면서도 중요한 부분입니다. 웹 사이트는 주로 마크업, 이미지, 스타일시트, 자바스크립트 등으로 구성됩니다. 각 구성 요소는 모두 웹서버에 있습니다. 이 구성 요소를 사용자의 컴퓨터로 가져오는 데는 네트워크 비용이 듭니다. 네트워크 비용은 곧 응답 시간으로 이어집니다. 그러므로 다운로드해야 하는 구성 요소의 개수를 줄이는 것은 가장 효과가 크고 중요한 최적화 방법입니다. 웹페이지에서 다운로드하는 구성요소의 개수는 HttpWatch나 YSlow로 알아볼 수 있습니다.

 

 

 

캐시가 없는 상태에서는 총 121개의 구성 요소를 다운로드 하고, 캐시가 있는 상태에서는 43개의 구성 요소를 다운로드합니다. 어떻게 121개의 구성요소만으로 구현해 HTTP 요청을 최소화할 수 있는지 살펴 보겠습니다.

 

CSS 스프라이트 기법 활용

이미지를 많이 사용하면 HTTP 요청이 많아질 수밖에 없습니다. 이미지를 많이 사용하면서도 HTTP 요청을 최소화하는 방법 가운데 하나가 CSS 스프라이트 기법입니다.

 

CSS 스프라이트 기법은 이미지 여러 개를 하나로 만들고 스타일시트에서 background-position 속성을 설정해 필요한 부분의 이미지만 보여주는 기술입니다. 여러 이미지를 하나의 이미지로 합치기 때문에 HTTP 요청 횟수를 줄일 수 있고, 이미지의 컬러 테이블과 같은 메타데이터를 하나로 합칠 수 있어 파일 크기가 줄어듭니다.

 

/* CSS 스프라이트용 이미지를 설정한다. */

.menu_list li a{background:url(http://example.com/some.png) no-repeat}



/* background-position 속성의 왼쪽과 위쪽 기준을 설정해 보여 줄 이미지를 지정한다. */

.menu_list a.me{background-position:0.0} /* 첫번째 아이콘 */

.menu_list a.mail{background-position:0 -52px} /* 52픽셀 아래에 있는 두번째 아이콘 */

 

 

CSS 스프라이트 기법은 이미지를 관리하기 어렵고 웹접근성을 나쁘게 하는 요소가 있다는 단점이 있지만 구성 요소를 다운로드하는 시간을 크게 줄여줍니다. 만약, CSS 스프라이트 기법을 사용하지 않았다면 60개의 아이콘을 총 60번의 요청으로 다운로드 받아야합니다. 이미지 하나를 받는데 0.05초가 걸린다고 가정하면 60개의 이미지를 받는 데 총 3초라는 시간이 걸립니다.

 

인터넷 익스플로러 8 버전의 동시 커넥션 수를 활용해 6개의 병렬 호스트에서 동시에 이미지 6개를 다운로드한다고 해도 60개의 이미지를 다운로드하려면 0.5초라는 시간이 걸립니다. 그렇지만 CSS 스프라이트 기법을 활용하면 이미지를 1개만 다운로드하면 되기 때문에 단 0.05초만에 필요한 이미지를 다운로드합니다.

 

 

헤더에 만료 날짜 추가

헤더에 만료 날짜를 추가하는 이유는 웹페이지를 구성하는 이미지, 스타일시트 파일, 자바스크립트 파일 등을 사용자 컴퓨터의 캐시에 저장해서 재사용하기 위해서입니다. 사용자가 처음 웹페이지에 방문하면 만료 날짜가 설정된 요소를 사용자 컴퓨터에 저장합니다. 이후 같은 웹페이지에 다시 방문하면 유효한(파일의 내용이 바뀌지 않고 사용 가능한) 요소는 서버에 요청하지 않고 사용자 컴퓨터에서 바로 읽어 옵니다. 이로 인해 상당히 많은 HTTP 요청을 줄여 웹페이지의 성능을 향상시킬 수 있습니다.

 

만약, 만료 날짜 전에 수정 사항이 있어 파일을 변경해야 한다면 파일 이름을 변경하거나 파일 이름 뒤에 쿼리스트링을 추가해 새로 추가된 파일임을 알려야 바로 반영됩니다.

 

// 방법1: 파일 이름을 변경한다.

<script type="text/javascript" src="some_20120622.js"></script>

 

// 방법2: 쿼리스트링을 추가한다.

<script type="text/javascript" src="some.js?20120622"></script>

 

브라우저에서 캐싱된 파일을 이용할지 서버에 요청할지 판단하는 기준은 파일이름과 인터넷 주소입니다. 그렇기 때문에 위와 같이 작업해서 파일 이름이나 파일의 주소를 바꾸지 않으면 계속 같은 파일로 인식하고 사용자 컴퓨터에 있는 파일을 로딩하게 됩니다.

 

 

만료 날짜 설정과 확인 방법

만료 날짜 설정은 웹서버에서 처리하는 내용입니다. 아파치 웹서버에서 만료 날짜를 설정하려면 mod_expires 모듈이 필요합니다. mod_expires 모듈을 설치한 다음 httpd.conf 파일을 수정해 만료 날짜를 설정합니다. httpd.conf 파일의 ExpiresActive 키워드로 만료 날짜 설정을 활성화하거나 비활성화합니다. 특정한 파일 형식에만 만료 날짜를 부여하려면 ExpiresByType 키워드를 활용합니다.

 

만료날짜는 HttpWatch나 Fidder를 이용하여 Header 정보를 확인 하면 됩니다. 만료 날짜가 1년 후로 설정되어 있다면, 사용자가 캐시파일을 지우기 전까지는 1년동안 계속 캐시파일을 읽습니다.

 

스타일시트 파일, 자바스크립트 파일, 심지어 일정 간격으로 변하는 콘텐츠에 포함된 이미지에도 만료 날짜를 설정해 얻은 성능 개선 결과입니다. 모바일 환경의 네트워크 상태는 PC 환경보다 불안하기 때문에 더 중요한 성능 개선요소라고 할 수 있습니다.

 

 

자바스크립트 파일 통합

웹서비스의 기능이 향상됨에 따라 자바스크립트 파일은 개수도 많아지고 크기도 커지고 있습니다. 이럴 때 성능을 높이는 방법은 여러개의 자바스크립트 파일을 하나의 파일로 합쳐 파일 개수를 최소화하는 것입니다.

 

성능 개선 사례로는 2008년 네이버 메일 3.0을 개발할 때 자바스크립트 파일의 개수는 무려 60개 정도였고, 캐시를 비운 상태에서 자바스크립트 파일을 로딩하는 데만 3초 이상의 시간이 걸렸습니다. 이 파일을 모두 합치고 최소화해 단 0.15초 만에 로딩하도록 성능을 개선했습니다.

 

웹 사이트의 성능을 개선할 때는 파일의 용량보다 파일의 개수가 더 중요합니다. 아주 용량이 작은 파일이라도 원격 서버에서 가져와야 한다면 네트워크 비용이 듭니다. 파일이 캐시에 있더라도 해당 파일이 유효한지 판단해야 합니다. 또한 병렬로 다운로드하는 데 한계가 있기 때문에 파일의 개수가 늘어나는 것은 성능에 치명적입니다.

 

 

 

파일 크기 최소화


웹페이지 구성 요소의 크기를 줄이는 것도 기본적인 웹사이트 최적화 방법 중 하나입니다.

 

Gzip 압축을 이용한 파일 크기 최소화

점점 커지는 자바스크립트 파일과 스타일시트 파일의 크기를 줄이는 가장 효과적이고 쉬운 방법은 파일을 압축하는 것입니다. 아파치 웹서버에서 파일을 압축하는 대표적인 인코딩 방식에는 Gzip과 deflate의 두가지가 있는데, deflate 방식은 지원하지 않는 브라우저가 많고 효과도 떨어지기 때문에 대부분 Gzip 방식을 사용합니다. Gzip 압축은 웹서버에서 설정하는 방법이라 여기서는 Gzip으로 압축한 파일이 어떻게 전달되고, Gzip으로 압축했을 때 어떤 효과가 있는지 살펴보면 다음과 같습니다.

 

압축 전송 흐름 및 확인

[1] 클라이언트에서 헤더 정보로 인코딩 여부를 물어본다.

[2] 서버에서 헤더 정보로 인코딩 여부를 알려준다. 인코딩된 요소라면 다음과 같이 응답이 온다.

[3] 클라이언트에서 인코딩된 요소를 받음과 동시에 압축을 해제한다. 이때 추가적인 CPU 연산 비용이 들어간다. 여기서 발생하는 CPU 연산 비용도 무시할 수 없기 때문에 압축할 파일과 크기를 잘 설정해야 한다. 보통 이미지 파일은 이미 압축돼 있기 때문에 압축하지 않고, 스타일시트 파일과 자바스크립트 파일을 압축한다. 그리고 파일 크기가 작으면 속도 개선 효과보다 CPU 연산 비용이 더 들기 때문에 파일 크기가 일정한 크기 이상인 경우에만 압축하는 것이 좋다. 스티브 사우더스의 "웹사이트 최적화 기법"에 의하면 파일 크기가 1~2KB 이상일 때 압축할 것을 권장한다.

 

Gzip으로 압축해 전송하면 평균 70% 정도 파일 크기가 작아지는 효과를 볼 수 있습니다. 모바일 환경과 같이 네트워크 환경이 불안한 상황에서는 더욱 효과적인 기술일 것입니다.

 

 

쿠키 크기 최소화

포털 사이트에서는 일반적인 웹사이트에서보다 쿠키의 크기가 더 큽니다. 공통으로 사용하는 로그인 정보도 있고 개별 서비스가 전체 사이트와 공유하는 정보가 많아집니다. 이 때 필요한 정보를 저장하는 가장 손쉬운 방법이 최상위 도메인을 이용해 쿠키를 설정하는 것이기 때문입니다. 이렇게 되면 자바스크립트 파일이나 스타일시트 파일, 이미지 등 쿠키 정보가 필요 없는 구성 요소를 요청할 때도 헤더 정보에 쿠키가 포함됩니다. 즉, 헤더를 전송할 때 데이터 크기가 커집니다.

 

사실 쿠키의 크기는 성능 관점에서 우선순위가 낮은 편이지만 쿠키를 지속적으로 관리하지 않으면 성능에 영향을 미칠 수 있습니다. 쿠키의 크기를 줄이는 기본적인 방법은 다음과 같습니다.

 

- 지속적인 관리로, 사용하지 않는 쿠키는 삭제한다.

- 쿠키를 설정할 때 최상위 도메인은 되도록 사용하지 않는다.

- 쿠키의 만료 날짜를 최대한 짧게(사용할 만큼만) 설정한다.

- 쿠키 정보가 필요 없는 이미지, 스타일시트, 자바스크립트 파일은 별도의 도메인으로 서비스한다. 최상위 도메인이 같지 않으면 쿠키 정보는 공유되지 않는 점을 활용한 방법이다.

 

최적화가 비교적 잘돼 있는 사이트에서 속도를 줄이기란 쉬운 일이 아닙니다. 쿠키 크기를 줄이는 것이 사소하게 느껴지지만 요청 헤더 크기가 하나 둘 커지다 보면 전체적으로 느려질 수 있습니다. 그렇게 때문에 쿠키 크기도 관심을 두고 관리해야 합니다.

 

 

렌더링 성능 향상


전체적인 로딩 속도는 동일한데 빈 페이지가 계속 보이다 갑자기 콘텐츠가 나타는 페이지가 있고, 처음부터 콘텐츠가 조금씩 보이며 화면이 빠르게 나타나는 페이지가 있습니다. 콘텐츠가 조금씩 보이는 화면이 더 빠르게 느껴지는 것은 체감 속도 때문입니다. 렌더링 성능 향상의 목표는 페이지를 요청했을 때 사용자가 대기하는 시간을 최대한 줄여서 이 체감 속도를 높이는 것입니다.

 

다음은 브라우저가 마크업을 파싱해서 화면에 보여주는 기본적 흐름입니다.

 

1. HTML 파싱과 DOM 트리 구성

사용자가 페이지를 요청하면 네트워크를 통해 마크업을 받아옵니다. 그러고 나서 마크업 문자열을 토큰 형태로 잘라서(Tokenizer) 트리를 구축하고 파싱 작업을 시작합니다. 그런 다음 DOM 트리를 생성 합니다.

2. 렌더 트리 구성(DOM + 스타일 규칙)

DOM 트리를 생성한 다음 바로 화면을 그리지는 않습니다. 스타일시트의 정보를 적용해야 하기 때문입니다. DOM 트리 정보와 스타일시트의 스타일 규칙을 결합해 렌더 트리(Render Tree) 를 만듭니다. display:none 속성처럼 DOM 트리에는 있지만 화면에 보이면 안되는 요소를 걸러낸 결과가 렌더 트리입니다.

3. 렌더 트리의 배치

최종적으로 스타일 규칙에 따라 각 요소를 화면의 어디에 배치할지 좌표를 설정 합니다.

4. 렌더 트리 그리기

요소의 좌표가 설정되면 브라우저에 순차적으로 화면을 그립니다. 이때 사용자는 화면을 조금씩 보게 됩니다.

 

기본적으로 위와 같은 단계를 거치는데 실수로 또는 잘 몰라서 이 흐름을 방해하는 방법으로 페이지를 개발하면 성능에 영향을 주게 됩니다. 어떻게 하면 성능에 영향을 주지않고 더 빠르게 화면을 보여줘 쾌적한 서비스를 제공할 수 있는지 알아보겠습니다.

 

 

스타일시트와 자바스크립트 배치를 이용한 성능 향상

스타일시트 파일은 페이지 제일 위쪽에 놓고 자바스크립트 파일은 페이지 맨 아래쪽에 놓아야 합니다.

 

브라우저 렌더링 단계에 따르면 사용자에게 화면을 보여 주기 전에 렌더 트리를 생성해야 하는데, 이때 스타일시트 파일이 반드시 필요합니다. 스타일시트 파일을 최대한 빨리 다운로드해야하는 이유입니다. 그리고 파이어폭스나 인터넷 익스플로러는 스타일시트 파일을 모두 다운로드할 때까지 화면을 렌더링하지 않고 기다립니다. 그래서 스타일시트 파일은 페이지의 제일 위쪽인 <head> 태그와 </head> 태그 사이에 놓아서 최대한 빨리 다운로드 해야합니다.

 

자바스크립트 파일을 페이지 아래에 놓아야 하는 가장 큰 이유는 파일을 다운로드해서 실행하기 전까지 브라우저가 DOM 파싱도 중지하고 아무것도 렌더링하지 않기 때문입니다. 자바스크립트에는 document.write() 메서드가 있어 마크업을 렌더링하는 도중에도 DOM을 추가할 수 있습니다. 이로 인해 이미 서버 통신을 완료하고 필요한 구성 요소를 모두 브라우저에 가져왔음에도 자바스크립트를 수행하느라 렌더링이 멈추게 됩니다. 이때 사용자에게는 마치 화면이 멈춘 것처럼 보여 체감 속도가 느려집니다.

 

 

초기 렌더링 시 Ajax 요청 최소화

동적인 웹사이트에서 화면을 그리는 단계는 일반적으로 다음과 같습니다.

 

1. 사용자가 페이지를 요청한다.

2. 서버에서 보낸 마크업을 다운로드해 렌더링을 시작한다. 이때 마크업은 화면을 구성하는 레이아웃만 있고 실제로 보여 줄 데이터는 나중에 AJAX 요청을 통해 받은 다음 그릴 것이다.

3. 자바스크립트 다운로드와 렌더링이 끝난 후 onload 이벤트가 발생한다.

4. onload 이벤트가 발생한 다음에야 AJAX 통신을 실행하고 데이터를 화면에 그린다.

5. 화면을 완성한다.

 

이 과정에는 두 가지 큰 문제점이 있습니다. 원래 AJAX 통신을 사용하지 않는 방법으로 페이지를 개발했다면 3번 단계에서 사용자는 화면을 보게 됩니다. 그런데 5번 단계까지 가서야 사용자는 최종화면을 볼 수 있습니다. 또 다른 문제는 렌더링이 반복된다는 것입니다. 1~3번 단계까지 전체 화면을 한 번 그리고 4~5번 단계에서 화면을 한 번 더 그립니다.

 

초기 응답 속도가 중요한 경우 AJAX 통신을 하지 않고 JSON 형태로 필요한 데이터를 받아 그려주는 방식으로 변경한다면 통신 비용만 덜었지 여전히 앞에서 발생한 문제가 그대로 나타나게 됩니다. 초기 렌더링 시에 마크업 전체를 서버에서 보내는 방식으로 개발하면 체감 속도를 높일 수 있습니다. 다시 말하면, 1~3단계에서 전체 화면과 데이터가 있는 화면을 모두 그리는 것입니다. 그리고 사용자의 행동이 있을 때 Ajax 요청을 실행해서 데이터를 받은 다음 화면을 그리게 하는 것으로 개선이 가능합니다.

 

초기 렌더링 시에 AJAX 통신으로 받은 데이터를 화면에 그리는 방법은 화면을 두 번 그리게 되어 체감 속도를 매우 느리게 합니다. 체감 속도를 높이려면 되도록 초기 렌더링시에는 AJAX 요청을 최소화합니다.

 

 

마크업 최적화

마크업 최적화의 목표는 빈 페이지가 한참 있다가 전체 화면이 한꺼번에 나타나는 것이 아니라 영역별로 차츰 렌더링하게 하는 것입니다. 끊김 없는 처리를 위한 여러 가지 방법이 있지만 여기서는 <table> 태그 처리에 대한 특성을 아는 것이 필요합니다.

 

DHTML 기술이 발표된 이후에는 페이지 전체의 레이아웃을 <table> 태그로 구성하는 방법을 사용하지 않게 되었습니다. 그러나 어쩔 수 없이 <table> 태그를 사용해야 하는 경우도 있고 기존에 만들어 둔 페지를 개편하지 못하는 경우도 생깁니다.

 

인터넷 익스플로러에서는 <table> 태그를 렌더링할 때 표 안에 있는 텍스트와 이미지 등을 모두 파싱할 때까지 화면 표를 그리지 않습니다. 만약 <body> 태그부터 시작해서 전체 화면을 그리는 데 <table> 태그를 사용했다면, 한참 있다가 전체 화면이 한번에 보일 것입니다. 반면에 파이어폭스에서는 <table> 태그가 모두 완료되기 전에도 표 안에 있는 각 요소가 보이기 때문에 부준적인 렌더링이 가능합니다.

 

모든 브라우저에 대응 가능한 렌더링 방법은 다음과 같습니다.

[1] HTML 코드가 올바른지 검사한다. http://validator.w3.org/ 사이트를 이용하면 손쉽게 검사할 수 있다.

[2] 태그의 중첩을 최소화한다. 다음과 같은 화면을 그리려면 태그가 어느 정도나 중첩됐을까? 외곽 테두리와 알림 문구, 이미지 태그 정도일 것이라 예상할 수 있지만 실제로는 7개 정도가 필요하다. 각 테두리에 그라이데이션 효과를 주기 위해 추가로 중첩된 마크업이 필요하다. 이런 디자인은 디자이너와 협의해 속도를 위해서 좋은 방법을 찾아야 한다.

 

javascript:alert(document.getElementsByTagName("*").length); void(0);

 

해당 페이지의 전체 태그 개수를 알 수 있는 코드는 위와 같다. 전체 태그 개수(보통 1000개 이하를 권장)를 줄이는 것도 중요하지만 중첩된 태그를 최소로 하는 것이 더 중요하다. 전체적으로는 태그의 개수를 줄이고 부분적으로는 중첩된 태그를 최소화해 간결하게 디자인하는 것이 렌더링 속도를 높이는 방법이다.

[3] 본문 전체를 <table> 태그로 감싸지 않는다.

[4] 되도록 <div> 태그와 스타일 시트를 이용해 레이아웃을 구성한다.

[5] 이미지 크기가 너무 크지 않게 한다.

 

 

웹 사이트 최적화시 우선 검토 사항


1. HTTP 요청을 최소화할 수 있는지 분석합니다.

 - HttpWatch와 같은 도구의 워터폴 차트로 웹페이지를 구성하는 요소에 무엇이 있는지 분석한다.

 - 이미지 요청 수를 분석하고 요청 수가 너무 많으면 CSS 스프라이트를 적용할 수 있는지 검토한다.

 - 스타일시트 파일이나 자바스크립트 파일의 개수를 분석하고 파일의 개수가 많다면 되도록 하나의 파일로 통합한다.

 - 불필요한 '404' 상태 코드가 나타나는 요소를 찾는다.

 - HTTP 요청에 대한 최적화 작업이 완료됐다면 재사용 방문자를 위해 캐시 설정을 진행한다.

 

2. 구성 요소의 크기를 줄입니다.

 - 자바스크립트 파일이나 스타일시트 파일에서 불필요한 공백이나 주석 등을 제거해 파일 크기를 최소화한다.

- Gzip 압축을 적용하면 파일의 크기를 더 줄일 수 있다.

 

3. 렌더링 향상을 위해서 아래와 같은 방법을 적용해 봅니다.

 - 스타일시트 파일은 페이지 제일 위에 놓고, 자바스크립트 파일은 페이지 제일 아래에 놓는다.

 - 페이지 초기 렌더링 시에는 AJAX 요청을 되도록 하지 않는다.

 - 한번에 페이지를 모두 보여 주기보다는 점진적으로 보여주는 방법을 적용한다.