옛날 옛적 브라우저가 캐시를 알아서 다 처리해주던 시절에 우리 개발자들은 캐시를 제어할 권한을 거의 갖지 못했다. 그런데 프로그레시브 웹 앱(PWA), 서비스 워커, 캐시 API가 등장했다. 별안간 캐시에 무엇을 어떻게 담을지 결정할 수 있는 권력이 우리 개발자들에게 주어진 것이다. 캐시에 무엇이든 깡그리 다 담을 수 있는 것은 좋은데 ··· 문제의 소지도 있다.
미디어 파일(특히 이미지)은 오늘날 평균적으로 웹 페이지의 하중에서 대부분을 차지하고 있는 데다 갈수록 무거워지고 있다. 미디어 콘텐츠를 모조리 캐시해 성능을 높이면 어떨까? 정말 그래도 될까? 대부분은 그러지 말아야 한다. 이 훌륭하신 신기술들이 우리 손가락 끝에서 발사 신호만 기다리고 있더라도, 고성능 웹페이지를 만들어내는 것은 여전히 기본을 지키는 일에 달려 있다. 그 기본 규칙이란 반드시 필요한 것만 요청해야 하며 각 요청의 크기를 최대한 작게 해야 한다는 것이다.
이 글에서는 여러분이 사용자의 네트워크 자원과 디스크 자원을 남용하지 않으면서도 최고의 사용자 경험을 제공할 수 있도록 안내하고자 한다. 고전적인 모범 사례들을 살펴보고, 미디어 캐시 전략을 실험해보고, 서비스 워커의 요술 주머니에 감춰둔 캐시 API 묘수들도 몇 가지 꺼내 놀아보자.
올바른 목적 의식
전화 모뎀 시절 웹 페이지를 최적화하면서 배웠던 교훈들은 모바일 시대를 맞이하며 다시 한번 매우 유용한 지침이 되었다. 그리고 오늘날 다국적 사용자들을 대상으로 한 서비스에도 유효하다. 여전히 세계의 수많은 지역에서는 네트워크 환경이 불안정하고 지연율도 높다. 이를 감안하면 최소 기술 사양이 균등하게 상승하거나 최신 기준과 보조를 맞출 것이라는 가정은 안전한 생각이 아니다. 그러므로 성능 최적화를 위한 모범 사례를 따르는 것이 중요하다. 오늘 성능에 도움이 되는 접근법이 미래에도 계속 성능에 도움이 된다는 것을 역사가 증명한 것이다.
자원마다 캐시 유지 기간을 어떻게 설정해 달라고 브라우저에 지시하는 것은 서비스 워커가 나오기 전에도 가능했다. 하지만 그게 다였다. 문서와 자원이 사용자의 단말기에 다운로드되면 하드 디스크 드라이브의 어느 디렉터리엔가 떨궈졌다. 그러면 나중에 브라우저가 특정 문서나 자원에 대한 요청을 준비할 때 네트워크에 손을 대지 않고 캐시에서 자원을 꺼낼 수 있는지 확인하고 사용하는 수준이었다.
요즘은 네트워크 요청과 캐시를 훨씬 더 세밀하게 제어할 수 있다. 하지만 그렇다고 우리가 웹 페이지의 자원에 관해 무관심해도 되는 것은 아니다.
반드시 필요한 것만 요청하라
언급했던 것처럼 오늘날 웹에는 미디어가 엄청나게 많다. 이미지와 비디오는 지배적인 의사소통 수단이 됐다. 이들이 세일즈나 마케팅 관점에서는 전환율을 높여줄지 모르겠지만 다운로드와 렌더링 속도라는 성능 관점에서는 좋게 볼 수가 없다. 이를 고려하면 모든 이미지(비디오 등도 포함)가 각각 페이지에서 스스로 자리를 쟁취하도록 해야 마땅할 것이다.
몇 년 전 어느 신문에서 스피릿(술. 유령이 아님) 요리에 관한 기사를 썼는데 이 기사에 내가 만든 조리법이 함께 실렸다. 나는 그 신문의 인쇄판을 구독하고 있지 않았기 때문에 기사가 어떻게 나왔는지 보려고 웹사이트에 접속했다. 때마침 신문사는 사이트를 리디자인하고 있었는데 그동안 홈페이지의 최상단에 거의 전체 화면에 가까운 모달 레이어를 띄우고 그 안에 모든 기사를 넣어두는 방식을 적용하고 있었다. 이는 모든 기사 내용과 각 기사 페이지에 필요한 모든 자원, 여기에 더해 홈페이지 내용과 홈페이지에 필요한 자원까지 다 요청해야 한다는 것을 의미했다. 오, 홈페이지에는 비디오 광고도 여러 개 들어 있었다. 그리고 당연하게도 자동재생도 됐다.
나는 개발자 도구를 열어 페이지의 무게가 15MB를 가뿐히 넘어서는 것을 확인했다. 당시 팀 카들렉Tim Kadlec이 발표했던 “내 웹사이트의 이용료는?What Does My Site Cost?”에 접속해서 그 피해를 확인해봤다. 미국 사용자 기준으로 그 페이지를 보는 데 드는 실제 비용은 그날 신문의 인쇄판을 사 보는 것보다 더 컸다. 그야말로 난장판이었다.
구독자에게 해를 입히는 사이트를 만든 사람들을 욕할 수도 있다. 하지만 현실적으로 볼 때 사용자 경험을 고의적으로 저해하려는 개발자가 어디 있을까? 이런 일은 우리 중 누구에게도 벌어질 수 있다. 우리가 페이지 성능을 끌어올리기 위해 몇 날 며칠을 들여 면밀히 조사했다 하더라도 그 잘 깎아놓은 페이지의 최상단에 자동재생 비디오 광고로 떡칠할 것을 무슨 경영회의 같은 곳에서 결정할 수도 있는 일이니 말이다. 극악한 성능을 뽐내는 페이지 두 개를 상호의존하도록 엮어서 만들어야 하는 상황이 얼마나 끔찍할까 상상해보라!
미디어는 경쟁이 심한 곳(예: 신문 홈페이지)에서 시선을 끄는 데는 탁월하지만 독자가 한 가지 일에 집중하기(예: 실제로 기사 읽기)를 원할 때는 미디어의 가치가 ‘중요함’에서 ‘있으면 좋지’로 떨어진다. 맞다. 이미지가 사용자 시선을 끄는 데 탁월하다는 연구가 많다. 하지만 일단 실제 페이지에 들어갔다면 아무도 관심이 없다. 이미지 때문에 다운로드 시간이 길어지고 접근 비용이 커질 뿐이다. 페이지에 미디어를 꾸역꾸역 담는 만큼 상황은 더 악화된다.
우리는 페이지의 무게를 줄이고 값어치 없는 요청을 방지하기 위해 모든 역량을 동원해야 한다. 가장 기초적인 실천은 무엇일까? 데이터 유출 사고에 관한 기사를 쓴다고 가정하면 누군가가 어두운 방에서 후드를 뒤집어쓴 채 컴퓨터에 무언가를 입력하고 있는 황당한 스톡 이미지를 넣어야 할 것만 같은 그 충동을 억누르는 것부터 해보자.
가능한 한 작은 파일을 요청하라
이제 꼭 필요한 미디어만을 추려냈다. 그 다음으로 스스로에게 매우 중요한 질문을 던져야 한다. 가장 빠른 방법으로 미디어를 전달하는 방법은 무엇인가? 이 질문에 대한 답으로는 이미지에 가장 알맞는 파일 형식을 선택하는 것(그리고 쥐어짜듯 최적화하는 것)처럼 간단한 답도 있고, 자원을 완전히 새로 만드는 것(예를 들어 래스터 이미지를 벡터 이미지로 바꾸면 더 효율적일 수 있다)과 같이 복잡한 답도 있다.
여러 가지 파일 형식을 제공하라
이미지 파일 형식을 결정할 때는 이제 더 이상 성능과 호환성 사이에서 양자택일하지 않아도 된다(압축 효율과 성능이 좋지만 아직 다양한 브라우저에서 지원되지 않아 채택하기 어려운 이미지 형식이 있다. 예를 들어 2020년 8월 기준 WebP 형식은 사파리와 인터넷 익스플로러에서 지원되지 않는다-옮긴이). 여러 가지 선택지를 준비해두고 브라우저가 적합한 것을 스스로 고르도록 하면 된다.
picture
또는 video
요소 안에서 여러 가지 sources
를 제공함으로써 이를 해결할 수 있다. 먼저 미디어 자원의 여러 가지 파일 형식을 생성한다. 예를 들어 WebP와 JPEG 중에는 WebP가 JPEG보다 파일 용량이 적을 가능성이 높다(하지만 실제로도 그런지 확인해야 한다). picture
를 이 두 가지 형식의 파일로 각각 준비한 뒤 다음과 같이 묶어서 제공할 수 있다.
See the Pen <a href=’https://codepen.io/wabooks/pen/YzqwNxR’>YzqwNxR</a> by wa (<a href=’https://codepen.io/wabooks’>@wabooks</a>) on <a href=’https://codepen.io’>CodePen</a>.
picture
요소를 인식하는 브라우저라면 source
요소를 함께 확인한 뒤에 어느 이미지를 요청할지 결정할 것이다. 브라우저가 “image/webp” MIME 유형을 지원하는 경우 WebP 형식의 이미지를 요청할 것이다. “image/webp”를 지원하지 않거나 picture
요소를 인식하지 못하는 브라우저에서는 JPEG를 요청할 것이다.
이 접근법은 사용자에게 가능한 한 적은 용량의 이미지 파일을 제공하면서도 모종의 자바스크립트 흑마술을 부릴 필요가 없어서 훌륭하다.
비디오 파일에도 동일한 방식을 활용할 수 있다.
See the Pen <a href=’https://codepen.io/wabooks/pen/NWNxdaq’>NWNxdaq</a> by wa (<a href=’https://codepen.io/wabooks’>@wabooks</a>) on <a href=’https://codepen.io’>CodePen</a>.
WebM을 지원하는 브라우저는 첫 번째 source
를 요청할 것이고, 그렇지 않고 MP4 비디오를 지원하는 브라우저는 두 번째 source
를 요청할 것이다. video
요소를 지원하지 않는 브라우저는 파일 직접 다운로드를 안내하는 단락을 출력할 것이다.
source
를 배치하는 순서도 중요하다. 브라우저는 지원 가능한 source
중 가장 먼저 배치된 것을 선택한다. 따라서 성능이 더 좋은 파일 형식을 호환성이 더 높은 파일 형식 뒤에 배치하는 경우, 성능이 좋은 파일 형식은 전혀 사용되지 않을 수도 있다.
경우에 따라서는 마크업 기반 접근법 대신 서버에서 처리하는 방법을 고려해야 할 수도 있다. 예를 들어, WebP를 지원(Accept
헤더에 표시)하는 브라우저에서 JPEG를 요청하는 경우에 서버가 WebP 버전의 자원을 응답하더라도 아무 문제가 되지 않는다. 클라우디너리Cloudinary와 같은 일부 CDN 서비스에서는 이러한 기능을 기본으로 제공하고 있다.
여러 가지 크기의 이미지를 제공하라
파일 형식과 별개로 브라우저 화면 영역(뷰포트) 크기에 최적화된 크기의 이미지를 제공하는 것도 고려해볼 수 있다. 이미지를 그리는 화면 크기보다 서너 배 큰 이미지를 제공하는 것은 네트워크 자원의 낭비일 뿐이다. 그러지 말고 반응형 이미지를 활용해보자.
다음은 반응형 이미지를 사용하는 예다.
See the Pen <a href=’https://codepen.io/wabooks/pen/NWNxdwG’>NWNxdwG</a> by wa (<a href=’https://codepen.io/wabooks’>@wabooks</a>) on <a href=’https://codepen.io’>CodePen</a>.
img
요소에 뭔가 잔뜩 채워져 있다. 무슨 일을 하는지 하나씩 살펴보자.
- 이 img 요소는 JPEG 파일의 크기를 세 가지로 제공한다. 각각 너비 기준 256px(
small.jpg
), 512px(medium.jpg
), 1024px(large.jpg
)이다. 예제와 같이srcset
특성에 이미지들을 너비와 함께 입력하면 된다. - src 특성은
srcset
특성을 지원하지 않는 브라우저를 위한 기본 이미지를 정의한다. 어느 이미지를 기본 이미지로 사용할 것인지는 문맥과 범용적인 사용 패턴을 함께 고려해 결정해야 한다. 가장 크기가 작은 이미지를 추천하지만 접속량의 다수가 기존 데스크톱 브라우저에서 이뤄진다면 중간 크기의 이미지를 선택하는 것도 좋다. sizes
특성은 브라우저가 이미지에 CSS를 적용해 다양한 시나리오에서 그려낼 때(그것의 외부 크기) 참고할 수 있는 정보다. 예제에서는 이미지의 가로 크기가 30em 이상인 경우(min-width: 30em
) 이미지의 가로 크기를 30em으로 설정하되, 그 외에는 이미지의 가로 크기를 화면 전체 가로 크기(100vw
)에 맞추도록 지시했다. 여러분의 필요에 따라 sizes 특성값을 복잡하게든 단순하게든 설정할 수 있으며, 생략하는 경우에는100vw가 기본값으로 사용된다.
여러 가지 파일 형식을 제공하는 방법과 여러 가지 크기를 제공하는 방법을 한 picture
에 결합하는 것도 가능하다.
여러분에게는 미디어를 빠른 속도로 제공하기 위한 도구가 이렇게 많이 있다. 잘 활용하자!
가능한 한 요청을 미뤄라
수년 전 IE11은 img
요소에 lazyload
라는 특성을 추가했다. 이 특성은 개발자가 특정 img 요소의 우선순위를 낮게 설정해 페이지 로드 속도를 높일 수 있게 한다. 이 특성이 모든 브라우저를 위한 표준안으로 채택되지는 못했지만 자바스크립트 없이 이미지가 실제로 필요할 때까지 이미지 로드를 지연할 수 있도록 한 시도로는 훌륭했다.
그후로도 자바스크립트 기반으로 이미지 로드 지연을 구현한 것이 많았다. 그리고 최근 구글 또한 img 요소에 새로운 특성을 추가하는 선언적 접근법을 제시했다. 바로 loading
특성이다.
loading
특성에는 자원이 언제 로드돼야 하는지를 auto, lazy, eager 세 가지 값으로 정의할 수 있다. 이 가운데 lazy 값이 가장 흥미롭다. lazy는 화면 표시 영역(뷰포트)과 자원이 위치한 곳 사이의 거리를 계산해 자원이 필요한 시점까지 자원의 로딩을 지연해준다.
앞서 정의한 img 요소에 loading 특성을 섞어보자.
See the Pen <a href=’https://codepen.io/wabooks/pen/qBZbRVK’>qBZbRVK</a> by wa (<a href=’https://codepen.io/wabooks’>@wabooks</a>) on <a href=’https://codepen.io’>CodePen</a>.
loading 특성을 활용하면 크롬 기반 브라우저에서 약간의 성능 향상을 얻을 수 있다. 장래에 loading 특성이 표준화돼 다른 브라우저에서도 채택된다면 좋을 것이다(2020년 8월 현재 엣지, 파이어폭스, 크롬, 오페라 등의 브라우저에서 지원된다. 정확한 지원 상태는 https://caniuse.com/#search=img%3A%20loading에서 확인할 수 있다-옮긴이). 하지만 그 전에도 미지원 브라우저는 단순히 loading 특성을 무시할 뿐이기 때문에 loading 특성을 활용해도 문제될 것이 없다.
이 접근법을 활용해 미디어 우선순위 전략을 제법 괜찮게 보완할 수 있다. 하지만 미디어 우선순위 전략에 대해 더 자세히 이야기하기 전에 서비스 워커를 자세히 살펴보는 것이 좋겠다.
서비스 워커로 요청을 제어하라
서비스 워커는 웹 워커(웹 워커는 브라우저에서 자바스크립트 코드를 백그라운드 스레드에서 실행할 수 있게 해주는 도구다-옮긴이)의 하나로, 서비스 워커에서는 페치 APIFetch API를 활용해 모든 네트워크 요청을 가로채고, 수정하고, 응답할 수 있다. 서비스 워커는 캐시 APICache API와 인덱스드DBIndexedDB로 대표되는 클라이언트 측 비동기 데이터 저장소에도 접근해 자원을 저장하고 불러올 수 있다.
서비스 워커가 설치되면 자원 요청 이벤트에 간섭해 나중에 사용할 자원을 캐시에 저장해둘 수 있다. 많은 개발자가 이를 활용해 전역 스타일, 스크립트, 로그 등의 전역 자원을 캐시에 채워놓고 있다. 이미지를 캐시해뒀다가 네트워크 요청이 실패한 경우에 사용하도록 하는 활용법도 가능하다.
비상용 기본 이미지를 뒷주머니에 챙겨둬라
기본 이미지 하나를 여러 네트워크 요청 레시피에 공통으로 적용하고자 한다고 가정하자. 다음과 같이 비상용 자원을 응답하는 함수를 정의할 수 있다.
See the Pen <a href=’https://codepen.io/wabooks/pen/rNexjpz’>rNexjpz</a> by wa (<a href=’https://codepen.io/wabooks’>@wabooks</a>) on <a href=’https://codepen.io’>CodePen</a>.
그후 요청 이벤트 처리 로직에서 이미지를 네트워크에서 가져오는 데 실패한 경우 이 함수를 호출해 기본 이미지를 제공할 수 있다. 요청 이벤트 처리기를 다음과 같이 작성하면 된다.
See the Pen <a href=’https://codepen.io/wabooks/pen/LYNGxQJ’>LYNGxQJ</a> by wa (<a href=’https://codepen.io/wabooks’>@wabooks</a>) on <a href=’https://codepen.io’>CodePen</a>.
네트워크 상태가 정상일 때는 사용자에게 정상적인 결과를 제공할 수 있다.
네트워크 상태가 불량할 때는 기본 이미지가 대신 사용되며, 사용자 경험이 그럭저럭 받아들일 만한 수준으로 유지된다.
겉으로 볼 때는 이 접근법이 성능 측면에서 그리 도움이 될 것처럼 보이지 않을 수도 있다. 다운로드해야 하는 이미지를 하나 더 추가하는 셈이니 말이다. 하지만 이 시스템을 채택함으로써 굉장한 새 가능성을 열 수 있다.
사용자의 데이터 절약 요구를 존중하라
사용자가 ‘라이트’ 모드나 ‘데이터 절약’ 기능을 켜서 데이터 소비를 절감하려는 경우가 있다. 이런 옵션이 활성화되면 브라우저는 네트워크 요청을 보낼 때 메시지에 Save-Data
헤더를 삽입한다.
서비스 워커에서 이 헤더를 확인하고 응답을 적절히 조절할 수 있다. 다음은 헤더를 확인하는 코드다.
See the Pen <a href=’https://codepen.io/wabooks/pen/poygRVR’>poygRVR</a> by wa (<a href=’https://codepen.io/wabooks’>@wabooks</a>) on <a href=’https://codepen.io’>CodePen</a>.
헤더를 확인한 뒤 이미지 요청 처리기에서 네트워크 요청을 하는 대신 기본 이미지를 응답하도록 할 수 있다.
See the Pen <a href=’https://codepen.io/wabooks/pen/ExKPZLQ’>ExKPZLQ</a> by wa (<a href=’https://codepen.io/wabooks’>@wabooks</a>) on <a href=’https://codepen.io’>CodePen</a>.
여기서 한걸음 더 나아가 보자. respondWithFallbackImage()
함수가 원본 요청의 성격에 따라 서로 다른 이미지를 응답하도록 하는 것이다. 이를 위해 먼저 서비스 워커에 여러 개의 기본 이미지를 설정한다.
See the Pen <a href=’https://codepen.io/wabooks/pen/gOrPgzV’>gOrPgzV</a> by wa (<a href=’https://codepen.io/wabooks’>@wabooks</a>) on <a href=’https://codepen.io’>CodePen</a>.
그리고 이 파일들을 서비스 워커 설치 중에 캐시에 저장하도록 한다.
See the Pen <a href=’https://codepen.io/wabooks/pen/WNwrRyw’>WNwrRyw</a> by wa (<a href=’https://codepen.io/wabooks’>@wabooks</a>) on <a href=’https://codepen.io’>CodePen</a>.
마지막으로 respondWithFallbackImage()
함수에서 요청하는 URL에 맞는 이미지를 응답하도록 한다. 내 사이트에서는 Webmention.io에서 프로필 이미지를 가져오기 때문에 이 주소를 검사하도록 했다.
See the Pen <a href=’https://codepen.io/wabooks/pen/abNdpKG’>abNdpKG</a> by wa (<a href=’https://codepen.io/wabooks’>@wabooks</a>) on <a href=’https://codepen.io’>CodePen</a>.
이와 함께 respondWithFallbackImage()
함수가 request.url
을 인자로 넘겨받을 수 있도록 수정하는 것도 필요할 것이다. 설정을 모두 마치면 네트워크 장애 상황에서 페이지가 다음과 같이 출력될 것이다.
다음 주제는 상황에 따른 미디어 자원 제어법의 일반 기준을 정립하는 것이다.
캐시 전략: 미디어의 우선순위 정하기
내 경험에 비춰보면 웹에 게시된 미디어(특히 이미지)는 얼마나 필요한가를 기준으로 했을 때 경향상 세 가지로 분류된다. 한쪽 극단에는 가치를 더해주지 못하는 미디어들이 있다. 반대쪽 극단에는 차트나 그래프와 같이 주변 내용을 이해하기 위해 꼭 필요하며 가치를 더하는 미디어들이 있다. 그 중간쯤에는 “있으면 좋다”라고 할 법한 미디어들이 위치한다. 페이지의 경험에 가치를 더해주기는 하지만 내용을 이해하는 데 반드시 필요하지는 않은 것들이다.
여러분의 미디어를 이와 같이 분류할 수 있음을 염두에 두고, 상황에 따라 각 미디어를 다루기 위한 일반적인 기준(즉 캐시 전략)을 설립하면 된다.
중요도를 기준으로 분류한 미디어 로드 전략
미디어 분류 | 빠른 네트워크 환경 | 데이터 절약 모드 | 느리 네트워크 환경 | 네트워크 없음 |
필수 | 다운로드 | 기본 이미지로 대체 | ||
있으면 좋음 | 다운로드 | 기본 이미지로 대체 | ||
불필요 | 제거 |
‘필수’ 미디어와 ‘있으면 좋은’ 미디어를 구분할 때 이들을 서로 다른 디렉터리에 모아두면 유리하다. 서비스 워커에서 각각을 구분하는 로직을 만들기 쉬워진다. 예를 들어, 내 개인 사이트에서는 필수 이미지들이 웹사이트 자체 또는 집필서 웹사이트에서 제공된다. 따라서 이 도메인들을 다음과 같이 정규식으로 지정할 수 있다.
See the Pen <a href=’https://codepen.io/wabooks/pen/JjXGEea’>JjXGEea</a> by wa (<a href=’https://codepen.io/wabooks’>@wabooks</a>) on <a href=’https://codepen.io’>CodePen</a>.
이렇게 high_priority
변수를 정의해두면, 특정 이미지 요청의 중요도가 높은지 아닌지를 판별하는 함수를 작성하는 데 쓸 수 있다.
See the Pen <a href=’https://codepen.io/wabooks/pen/PoNZWXw’>PoNZWXw</a> by wa (<a href=’https://codepen.io/wabooks’>@wabooks</a>) on <a href=’https://codepen.io’>CodePen</a>.
미디어의 우선순위를 가리는 기능을 추가하기 위해 필요한 것은 Save-Data
헤더를 지원할 때처럼 fetch
이벤트 처리기에 분기를 하나 더 추가하는 것뿐이다. 여러분 각자의 네트워크 및 캐시 처리 레시피와는 다를 가능성이 높지만 다음은 내가 이미지 요청 처리기에 적용한 방식이다.
See the Pen <a href=’https://codepen.io/wabooks/pen/XWdXpoZ’>XWdXpoZ</a> by wa (<a href=’https://codepen.io/wabooks’>@wabooks</a>) on <a href=’https://codepen.io’>CodePen</a>.
이와 같이 우선순위를 반영한 접근법을 다양한 자원에 적용할 수 있다. 각 페이지마다 제공 방식(캐시 우선 vs. 네트워크 우선)을 달리하는 것도 가능하다.
캐시를 말끔하게 유지하라
자원을 디스크에 캐시할 수 있는 권한은 여러분에게 큰 가능성을 열어준다. 하지만 그 힘을 오남용하지 말아야 한다는 큰 책임 또한 따른다.
각각의 캐시 전략은 저마다 조금씩 차이가 있을 것이다. 예컨대 온라인 도서를 출간한다면 책을 오프라인 상태에서도 볼 수 있도록 모든 장의 내용과 삽화 등을 캐시하는 것이 알맞은 결정이 될 수도 있다. 책의 내용은 일정한 분량으로 정해져 있을 것이고 너무 무거운 이미지나 비디오는 없을 테니까 장마다 별도로 다운로드하도록 하지 않는 편이 사용자에게 더 좋을 수 있다.
반면 뉴스 사이트를 예로 들면, 모든 기사와 사진을 캐시하도록 할 경우 사용자의 하드 드라이브를 순식간에 잠식해버릴 것이다. 사이트에서 제공하는 페이지나 자원의 수량이 한정되지 않은 경우, 디스크에 캐시할 자원의 양을 일정 수준으로 제한하는 캐시 전략이 반드시 필요하다.
각 콘텐츠의 유형에 대응하는 캐시 방식을 각각 서로 다른 블록으로 작성해두는 것이 한 방법이다. 유효기간이 짧은 콘텐츠일수록 저장량을 더 깐깐하게 제한해볼 만하다. 물론 이렇게 고려하지 않더라도 어차피 저장 장치의 용량 제한을 넘길 수는 없다. 하지만 그렇다고 우리 웹사이트가 사용자 하드 디스크를 2GB씩 차지하는 것이 옳겠는가?
다음은 내 개인 사이트의 예다.
See the Pen <a href=’https://codepen.io/wabooks/pen/KKzVaJW’>KKzVaJW</a> by wa (<a href=’https://codepen.io/wabooks’>@wabooks</a>) on <a href=’https://codepen.io’>CodePen</a>.
여러 벌의 캐시 그룹을 정의했다. name
속성은 캐시 API에서 각 캐시를 식별하기 위한 이름이며 version
접두사도 붙였다. versio
n은 서비스 워커 코드의 다른 부분에 정의해뒀으며 version을 변경하면 기존 캐시를 전부 무효화할 수 있다.
정적 자원을 캐시하기 위한 static
캐시 외의 모든 캐시에는 저장할 수 있는 항목의 수를 limit
값으로 정의해뒀다. 즉 최근 다섯 페이지까지, 이미지는 최대 75개까지 저장하도록 했다. 이 접근법은 제레미 키스의 환상적인 저서 《서비스 워커로 만드는 오프라인 웹사이트Going Offline》에서 살펴볼 수 있다(아직 읽어보지 않았다면 꼭 읽어봐야 할 책이다. 견본 보기).
캐시를 이렇게 정의해두면 오래된 항목들을 주기적으로 정리할 수 있다. 다음 코드는 제레미 키스가 제안한 방식이다.
See the Pen <a href=’https://codepen.io/wabooks/pen/BaKjpbj’>BaKjpbj</a> by wa (<a href=’https://codepen.io/wabooks’>@wabooks</a>) on <a href=’https://codepen.io’>CodePen</a>.
페이지를 새로 로드할 때마다 이 코드를 실행하도록 할 수 있다. 이를 서비스 워커에서 수행하면 독립 스레드로 실행되므로 사이트의 반응성을 저해하지 않는다. 자바스크립트 메인 스레드에서 postMessage()
로 메시지를 발송해 코드를 실행하면 된다.
See the Pen <a href=’https://codepen.io/wabooks/pen/ExKPZMR’>ExKPZMR</a> by wa (<a href=’https://codepen.io/wabooks’>@wabooks</a>) on <a href=’https://codepen.io’>CodePen</a>.
서비스 워커가 메시지를 받을 수 있도록 설정을 마무리하는 일만 남았다.
See the Pen <a href=’https://codepen.io/wabooks/pen/wvGMgZG’>wvGMgZG</a> by wa (<a href=’https://codepen.io/wabooks’>@wabooks</a>) on <a href=’https://codepen.io’>CodePen</a>.
이렇게 하면 서비스 워커가 메시지를 수신하다가 “clean up” 요청을 받는 경우 trimCache()
를 실행해 캐시를 정의된 항목 수 이하로 정리하게 된다.
이 방식이 우아하다고 할 수는 없겠지만 어쨌거나 동작은 한다. 삭제할 항목을 판단할 때 캐시된 항목의 활용 빈도와 용량의 비중 등을 종합적으로 고려하는 것이 훨씬 좋을 것이다(캐시된 시점만 단순히 따지는 것은 그리 효율적이지 못할 것이다). 안타깝게도 캐시를 점검할 때 그 정도로 자세한 정보는 제공되지 않는다. 나는 캐시 API에 이런 한계를 지적하는 중이다.
항상 사용자를 우선하라
프로그레시브 웹 앱을 지탱하는 기술들은 점점 더 완성돼가고 있다. 설령 여러분이 사이트를 PWA로 전환하는 데 관심이 없다 하더라도 당장 사용자 경험을 개선하기 위해 할 수 있는 일들은 매우 많다. 또한 여타 포괄적 디자인 양식과 마찬가지로 언제나 사용자를 중심에 두는 것이 출발점이다. 사용자야말로 형편없는 사용자 경험에 의한 가장 큰 피해자이기 때문이다.
필수 미디어, 있으면 좋은 미디어, 잉여 미디어 사이에 선을 명확히 그어라. 잡스러운 것들을 제거하고 나머지 자원들을 군더더기 없이 깎아내라. 미디어를 다양한 파일 형식과 크기로 제공하고, 용량이 가장 적은 것을 우선순위에 배치해 다양한 네트워크 환경을 최대한 지원하라. 사용자가 데이터를 절약하고자 한다면 이를 존중하고 그에 대응하는 자원을 마련하라. 캐시를 현명하게 활용하고 사용자의 디스크 공간을 존중하며 아껴 써라. 그리고 마지막으로 캐시 전략을 주기적으로 검토하라. 특히 대용량 미디어 파일인 경우에 더 신경 써야 한다. 이 지침을 잘 따른다면 인도 산간 오지에서 지오폰JioPhone을 사용하는 사람들부터 실리콘 밸리에서 10Gbps의 광케이블에 연결된 최고 사양의 게임용 노트북을 사용하는 사람들까지 모든 사용자가 당신에게 고마워할 것이다.
제레미 키스의 《서비스 워커로 만드는 오프라인 웹사이트》
웹 기술이 지금처럼 강력했던 적은 없었다. CSS를 이용해 놀라운 레이아웃을 만들고, SVG로 이미지를 가볍게 최적화해 배포할 수 있고, 자바스크립트 API로 단말기의 다양한 센서에 접근할 수 있다. 여기에 서비스 워커가 더해지면 오프라인 기능까지 구현할 수 있는 것이다. 이 책은 진정으로 회복력 있는 웹사이트를 어떻게 디자인해야 하는지 보여준다.
books@webactually.com