보통 웹 사이트의 개발자 도구에서 네트워크 탭을 보면 한번에 많은 이미지 파일을 서버로 부터 불러오고 있는 것을 볼 수 있다.
이 때 웹 페이지에 사용되고 있는 이미지가 많다고 가정한다면, 어떤 문제가 발생할까?
100개의 이미지가 사용되었다고 가정하면 웹 브라우저는 100번의 요청을 서버로 보내야한다.
하지만, HTTP 요청은 많아지면 많아질 수록 페이지 로딩 속도가 느려진다.
왜 그럴까? 먼저 HTTP 요청 처리 과정을 살펴보자.
해당 이미지를 참고해서 상세하게 어떤 과정이 일어나는지 살펴보자. 이는 개발자 도구 네트워크 탭에서 각 request에 대한 타이밍 탭에서 확인할 수 있다. 이미지에 없는 단계가 하위 리스트에 존재할 수 있다.
대기열(Queueing)
브라우저가 연결이 시작되기 전에 우선순위가 높은 요청을 큐에 추가해둔다.
우선순위는 CSS, 글꼴, 스크립트, 이미지와 같은 리소스의 유형, 문서에서 리소스를 참조하는 위치 또는 순서, 스크립트에 async 또는 defer 속성이 있는지 여부에 따라 결정된다. 대기열은 브라우저 디스크 캐시에 할당되어 있다.
중단됨(Stalled)
TCP 연결이 세팅되고 실제로 데이터를 전송할 수 있는 시간 사이의 시간 차이로, 프록시 협상 시간이 포함된다.
DNS 조회(DNS Lookup)
DNS 조회에 소요된 시간을 뜻하며, 페이지의 각 도메인마다 DNS 조회가 필요하다.
초기 연결(Initial connection)
TCP handshake/retires 및 SSL negotiation을 포함하여 연결을 설정하는 데 소요된 시간이다.
SSL
SSL handshake를 완료하는 데 소요된 시간.
Proxy negotiation
proxy 서버와 협상하는 데 소요된 시간이다.
요청 전송됨(Request sent)
네트워크 요청을 전송하는 데 소요된 시간(일반적으로 밀리초)이다.
Service Worker Preparation
Service Worker가 준비되는 데 소요된 시간이다. Service Worker는 웹 응용 프로그램, 브라우저, 네트워크 사이의 프록시 서버 역할을 한다.
Request to Service Worker
Service Worker로 요청을 보내는 데 소요된 시간이다.
서버 응답을 기다리는 중(Waiting)
TFFB는 Time To First Byte의 약자로 페이지 요청이 이루어진 후부터 응답 데이터의 첫 번째 바이트를 수신할 때까지의 시간이다.
이 타이밍에는 1회의 왕복 지연 시간과 서버가 응답을 준비하는 데 걸린 시간이 포함된다.
콘텐츠 다운로드(Content Download)
브라우저가 네트워크나 서비스 워커로부터 응답을 수신하는 단계로, 이 값은 response body를 읽는 데 소요된 총 시간. 예상보다 큰 값은 네트워크가 느리거나, 브라우저가 응답을 읽는 것을 지연시키는 다른 작업을 수행하느라 바쁘다는 것을 나타낼 수 있음.
위와 같은 단계들은 각 HTTP 요청마다 발생한다. 따라서 HTTP 요청이 많아지면 많아질 수록 이러한 단계들이 반복적으로 발생하게 되기 때문에 비효율적으로 작동하게 된다.
이는 이미지 요청에 대한 타이밍 분석이다. 확인해보았을 때 83.38ms 중 콘텐츠 다운로드 시간이 차지하는 것은 5.77ms로 6.92%밖에 차지하지 않는다. 콘텐츠 파일의 크기가 커져도 요청-응답 소요 시간에서 콘텐츠 다운로드가 차지하는 시간은 크지 않을 것이다. 이런 상황에서 페이지에 이미지가 많다고 가정하면 어떻게 해서 HTTP 요청 수를 줄일 수 있을까?
요청 자체에 소요되는 시간이 많으니 여러 개의 작은 이미지를 하나의 이미지에 통합하여 HTTP 요청 수를 줄일 수가 있다.
대표적인 예로 네이버 메인 페이지에서 스프라이트 기법을 사용하고 있다. 동적으로 적용되는 이미지들이 아닌 항상 고정되어 사용되는 아이콘 같은 것들이 하나의 이미지에 모두 포함되고 있다.
스프라이트 이미지는 CSS Sprites Generator를 통해서 쉽게 생성할 수 있다.
생성한 이미지를 잘라 적용하는 방법은 비교적 간단하다. CSS의 background-image
와 background-position
속성을 활용하여 하나로 통합된 큰 이미지 내에서 필요한 부분만 자를 수가 있다.
background-position
속성은 이미지의 위치를 조정하는 속성으로, x축과 y축의 좌표를 지정하여 원하는 부분을 표시할 수 있다.
background-image
속성은 스프라이트 이미지의 경로를 지정하는 속성이다.
CSS Sprites Generator로 스프라이트 이미지를 생성하면 각각의 아이콘에 대한 x축과 y축 좌표를 얻을 수 있기에 따로 계산하거나, 측정 도구를 사용하지 않고 간편하게 이미지의 위치를 적용해줄 수 있다.
.bg-rightTab {
width: 20px; height: 20px;
background: url('css_sprites.png') -10px -10px;
}
.bg-iconTrash__1_ {
width: 20px; height: 20px;
background: url('css_sprites.png') -50px -10px;
}
네이버 스프라이트 이미지에서 본 것과 같이 보통 고정되어 변경되지 않는 이미지들을 대상으로 적용하는 게 좋을 수 있다.
변경이 잦다면 매번 스프라이트 이미지를 생성해야 하기 때문에 유지 보수 측면에서 좋지 않을 수 있다.
따라서 상황에 따라 적용 여부를 결정하는 것이 좋다.
하나의 이미지로 여러 군데 이미지를 적용하고 있기 때문에 만약 스프라이트 이미지에 문제가 생긴다면 해당 이미지가 사용되는 모든 곳에 영향을 미치게 된다. 따라서 스프라이트 이미지의 크기가 커지면 커질수록 문제가 발생할 확률이 높아질 수도 있다.
HTTP/2에서는 request를 병렬로 처리할 수 있기 때문에 HTTP 요청 수를 줄이는 것이 큰 의미가 있을까에 대한 논의가 이루어졌다. HTTP/2는 HTTP/1.1에 비해 로드 시간을 확실히 감소시키지만, 여전히 HTTP 요청 수가 많아질 수록 페이지 로딩 시간에 영향을 주고 있는 것을 그래프를 통해 확인할 수 있다.
저도 사내에서 이미지 스프라이트 아이콘을 적용한적이 있었는데, 혹시 적용해보셨는지 만약 적용하셨을때 색상을 동적으로 변경하는 것에 대해서는 문제가 없으셨는지 궁금합니다!