05-브라우저 캐시 마스터하기

업데이트: Link

브라우저 캐시 마스터하기

Part 5 of 6 in our Vue.js Performance series.
Written by Filip Rakowski

지금까지 Vue.js 성능 시리즈에서는 번들 크기에 중점을 두었습니다. 확실히 앱 성능의 가장 중요한 측면 중 하나이지만 유일한 측면은 아닙니다. 빠른 웹 애플리케이션을 구축하려면 가능한 가장 작은 자산을 제공하는 것 만 중요한 것이 아닙니다. 또한 사용자가 이미 다운로드한 항목을 기다리지 않도록 가능하면 재사용하는 것이 중요합니다. 이 시리즈의 이전 부분을 읽었다면 해당 분야에서 확신할 수 있을 만큼 번들 크기에 대해 충분히 알고 있을 것입니다. 이것이 이 기사에서 로딩 시간을 크게 향상시킬 수 있는 또 다른 기술인 캐싱에 초점을 맞추는 이유입니다.

브라우저 내장 캐시

가장 자주 방문하는 페이지가 다른 페이지보다 조금 더 빨리 로드된다는 사실을 알고 계셨습니까? 이것은 우리 브라우저가 미래에 잠재적으로 유용할 파일을 캐시할 만큼 충분히 똑똑하기 때문에 발생합니다.

캐싱은 일반적으로 사용되는 데이터를 빠르게 액세스할 수 있는 메모리에 저장하여 요청한 직후에 사용할 수 있도록 하는 프로세스입니다. 그 때문에 우리 브라우저는 때때로 네트워크를 생략하고 우리 자신의 컴퓨터에서 정기적으로 사용하는 파일에 즉시 액세스할 수 있습니다.

그렇다면 캐시는 얼마나 빠를 수 있습니까?

가장 빠른 것은 CPU 캐시입니다.

  • L1 캐시는 현재 사용 중인 데이터를 보관하는 역할을 합니다. 공간이 정말 작기 때문에 가장 빠르게 액세스할 수 있습니다(~1-2ns).
  • L2 캐시는 순간에 필요할 수 있는 데이터를 유지합니다(~4ns).

최신 CPU에는 때때로 CPU 코어 간에 공유되는 메모리로 사용되는 L3 캐시도 있습니다(각 코어에는 일반적으로 고유한 L1 및 L2 캐시가 있습니다).

CPU 캐시 외에도 훨씬 더 클 수 있는 RAM 메모리가 있지만 액세스 시간도 훨씬 더 높습니다(L1보다 평균 100배 느림). 가장 느린 메모리는 하드 드라이브입니다.

우리 브라우저는 캐시를 메모리 캐시(브라우저가 닫힐 때까지 유지) RAM에 저장하고 디스크 캐시에 저장할 수 있습니다. 이 캐시는 훨씬 더 오래 지속되지만 시간이 오래 걸리는 하드 드라이브에 저장됩니다. 액세스하는 데 훨씬 더 오래 걸립니다.

직접 볼 수 있습니다. 웹사이트에서 개발자 도구의 네트워크 탭을 열고 몇 번 새로 고칩니다. CSS, HTML, JavaScript 파일 및 이미지와 같은 정적 자산이 메모리 캐시의 서버임을 알 수 있습니다.

이제 브라우저를 닫고 같은 페이지를 다시 엽니다.

디스크 캐시에서 동일한 자산이 제공되는 것을 볼 수 있습니다! 캐시 헤더에 지정된 한 메모리에 보관됩니다. 이것이 브라우저를 닫은 후에도 자주 방문하는 페이지가 다른 페이지보다 빠르게 로드되는 이유입니다.

가장 빠른 CPU 캐시의 이점을 얻을 수는 없지만 사용 가능한 두 스토리지는 불확실한 네트워크 조건에서 완료하는 데 몇 초도 걸릴 수 있는 네트워크 요청보다 훨씬 빠르게 응답합니다.

브라우저 캐시에 대한 번들링

방금 보았듯이 사용자가 브라우저 캐싱의 이점을 누릴 수 있도록 하기 위해 아무 것도 할 필요가 없습니다. 브라우저가 스스로 모든 것을 잘 처리하는 것 같습니다!

그 기능을 더 잘 활용하기 위해 우리가 할 수 있는 일이 있습니다. 자주 변경되지 않는 앱 부분과 더 정기적으로 변경되는 부분을 분리할 수 있다면 웹사이트의 적어도 일부가 브라우저 캐시의 이점을 누릴 수 있도록 할 것입니다.

다음 자산을 요청하는 웹 앱을 상상해 보십시오.

  • app.[hash].js: 루트 구성 요소 및 앱 진입점 포함
  • common.[hash].js: vue vue-router vuex 등과 같은 모든 경로에서 공통 종속성을 사용합니다.
  • home.[hash].js: 페이지별 종속성이 있는 홈 페이지 청크
  • product.[hash].js: 페이지별 종속성이 있는 제품 페이지 청크

여기서 가장 중요한 부분은 common.[hash].js 파일입니다. 언제 요청되고 언제 무효화됩니까?

이 파일은 두 경로 모두에서 필요하며 새로운 앱 수준 종속성을 추가하는 경우에만 무효화됩니다(파일을 수정하면 해시가 변경됨). 그 때문에 변경 가능성이 훨씬 적고 경로 코드가 변경되어 다시 다운로드해야 하는 경우에도 캐시에서 재사용할 수 있습니다. 앱에서 일반적이고 변경이 덜한 부분을 별도의 번들로 두면 사용자가 기다리는 시간을 줄일 수 있습니다!

모든 루트 수준 종속성을 하나의 파일에 넣을 필요는 없습니다. 때로는 각 종속성(여기 이 접근 방식에 대한 매우 좋은 분석을 찾을 수 있습니다.) 또는 특정 그룹에 대해 별도의 파일을 가질 가치가 있습니다. 이 경우 새 파일을 추가해도 전체 common.js 파일이 무효화되지 않습니다.

브라우저 캐시가 충분하지 않은 경우

브라우저 캐시에 최적화된 코드를 만들기 위해 할 수 있는 일이 많이 있으며 결코 낭비가 아니지만 브라우저 캐시에는 많은 제한이 있습니다.

첫 번째 문제는 크기입니다. 브라우저 캐시는 다소 작기 때문에 캐시된 자산 중 일부를 새 자산으로 쉽게 재정의할 수 있습니다. 이것은 일반적으로 우리가 많은 웹사이트를 탐색할 때 발생합니다.

또 하나는 불확실한 행동입니다. 각 브라우저 엔진은 다른 것과 약간 다른 자체 캐싱 메커니즘을 구현합니다. 또한 일부 장치 설정이 작동 방식에 영향을 줄 수 있습니다.

아마도 브라우저 캐싱의 가장 큰 문제는 우리는 개발자로서 행동을 조정하는 대신 행동에 적응해야 한다는 것입니다. 앱을 만든 개발자보다 애플리케이션 자산을 다루는 방법을 더 잘 아는 사람은 없습니다. 캐싱을 담당해야 하는 것은 우리입니다!

또한 안정적인 네트워크 연결이 있는 경우에만 브라우저 캐시에서 파일을 검색할 수 있습니다. 우리가 오프라인 상태라면 우리가 거기에 무엇을 가지고 있든 항상 같은 그림을 보게 될 것입니다.

고맙게도 얼마 전에 Google은 위의 문제를 쉽게 해결할 수 있는 두 가지 브라우저 기능인 서비스 워커와 캐시 API를 표준화했습니다.

서비스 워커 및 캐시 API

web.dev에서 다음을 읽을 수 있습니다. 캐시 API 정보:

Cache API는 네트워크 요청 및 해당 응답을 저장 및 검색하기 위한 시스템입니다. 이는 일반 요청 및 응답일 수 있습니다. 애플리케이션을 실행하는 과정에서 생성되거나 나중에 사용하기 위해 데이터를 저장하기 위한 목적으로만 생성될 수 있습니다.”

따라서 Cache API는 우리가 완전히 제어할 수 있는 네트워크 응답을 위한 저장소입니다.

이제 서비스 워커란 무엇입니까? 이 용어는 이미 들어보셨을 것입니다. 웹 개발자 커뮤니티에서 여전히 뜨거운 주제입니다. 간단히 말해서 서비스 워커는 앱과 다른 스레드를 실행하고 브라우저와 네트워크 사이에서 프록시처럼 작동하는 JavaScript 코드입니다. 이는 웹 앱에서 네트워크 요청 및 응답을 완전히 제어할 수 있음을 의미합니다! 요청 URL, 매개변수 또는 응답까지 변경할 수 있습니다. 어디로 가는지 보이시죠?

서비스 워커와 캐시 API를 사용하면 특정 네트워크 요청에 대한 응답으로 이전에 캐시에 넣은 특정 파일을 프로그래밍 방식으로 제공할 수 있습니다. 이제 캐시를 무효화할 때와 캐시할 항목을 완전히 제어할 수 있습니다. 서비스 워커가 프록시 역할을 하기 때문에 연결이 안정적이지 않을 때 캐시 스토리지에 대한 모든 네트워크 요청을 프록시하여 애플리케이션이 오프라인으로 작동하도록 할 수도 있습니다. 얼마나 멋진데요?!

또한 더 이상 매우 엄격한 메모리 제한으로 제한되지 않으므로 훨씬 더 많은 자산을 캐시할 수 있습니다!

가능성이 너무 많기 때문에 이러한 상품을 활용하는 가장 좋은 방법이 무엇인지 궁금할 것입니다. 다행히 이 조합에 대한 모범 사례가 이미 확립되어 있습니다.

The App Shell model

App Shell 아키텍처는 거의 즉시 로드되는 웹 앱을 구축하는 가장 효율적인 방법 중 하나입니다. 앱 셸은 앱이 작동하는 데 필요한 최소한의 정적 자산(CSS. HTML, JS, 이미지) 집합입니다. 이러한 자산은 캐시에 저장하고 다른 방문 시 캐시에서 직접 제공해야 합니다.

이제 흰색 화면 대신 동적 콘텐츠가 백그라운드에서 다운로드되는 동안 사용자는 즉시 응용 프로그램의 인터페이스를 볼 수 있습니다. 우리는 사용자에게 즉시 무언가를 제공하기 때문에 App Shell 모델을 사용하는 애플리케이션의 이탈률이 훨씬 낮습니다!

The App Shell Model - Web Fundamentals - Google Developers

이 개념은 매우 간단하면서도 정말 강력합니다! Google 개발자의 노력 덕분에 구현도 똑같이 간단합니다.

Workbox를 사용하여 앱 셸 캐시

서비스 워커와 캐시 API를 마스터하는 것은 시간 소모적인 과정일 수 있지만 고맙게도 복잡한 API인 Workbox를 배우지 않고도 이러한 브라우저 기능의 이점을 누릴 수 있는 훌륭한 라이브러리가 있습니다.

CLI에서 몇 가지 간단한 질문에 답하면 앱 셸을 캐시할 즉시 사용할 수 있는 서비스 워커를 생성할 수 있습니다. 새로운 Vue CLI 프로젝트에서 이 작업을 수행하는 방법을 살펴보겠습니다.

먼저 Workbox CLI를 설치해야 합니다.

npm install workbox-cli --global

다음 단계는 앱 루트 디렉토리로 이동하여 CLI용 구성 파일을 생성할 마법사를 설정하는 것입니다. 프로덕션 파일 위치, 캐시 대상 등을 지정합니다.

workbox wizard

제공된 답변을 기반으로 워크박스 마법사는 유사한 콘텐츠가 포함된 workbox-config.js 파일을 생성합니다.

module.exports = {
  "globDirectory": "dist/",
  "globPatterns": [
    "**/*.{css,ico,png,html,js}"
  ],
  "swDest": "dist/sw.js"
};

구성이 완료되면 서비스 워커 자체를 생성할 차례입니다. 다음 명령으로 이를 수행할 수 있습니다.

workbox generateSW workbox-config.js

서비스 워커는 dist/sw.js에서 찾을 수 있습니다. 이제 남은 일은 앱에 서비스 워커를 등록하는 것뿐입니다. 다음 스니펫을 main.js에 붙여넣으면 됩니다.

// Check that service workers are supported
if ('serviceWorker' in navigator) {
  // Use the window load event to keep the page load performant
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/sw.js');
  });
}

그게 끝입니다! 이제 콘솔을 연 후 앱을 빌드하고 프로덕션 모드에서 실행하면 앱 셸이 성공적으로 캐시되었다는 Workbox 메시지가 표시되어야 합니다!

캐시에 어떤 파일이 있는지 확실하지 않은 경우 Chrome Devtools의 “응용 프로그램” → “캐시 저장소” 탭에서 파일을 찾을 수 있습니다.

네트워크 연결을 끄고 페이지를 새로 고침해도 화면에 캐시된 앱 셸이 계속 표시됩니다. 앱을 오프라인에서 사용할 수 있도록 만들기 시작하기에 완벽한 장소입니다.

TIP: Vue CLI용 PWA 플러그인Nuxt.js용 PWA 모듈.

동적 요청 캐싱

앱 셸을 캐싱하면 인지된 성능이 크게 향상될 수 있지만 앱의 로드 시간을 개선하기 위해 캐싱할 수 있는 덜 중요한 자산이 많이 있습니다. 이러한 자산은 일반적으로 덜 중요하거나 자주 변경되므로 모든 자산을 미리 다운로드하면 시간과 대역폭이 낭비될 수 있습니다. 이러한 요청에 대해 런타임 캐싱을 사용할 수 있습니다.

모든 중요한 정적 자산을 미리 캐시하는 이전 방법과 달리 앱에서 자연스럽게 초기화된 네트워크 응답만 캐시합니다. 예를 들어 전자 상거래 웹 사이트의 제품 페이지에 들어갈 때 제품 정보를 포함하는 JSON 개체를 캐시할 수 있으므로 다음에 이 특정 제품을 방문할 때 기다릴 필요가 없습니다.

런타임 캐싱을 활성화하려면 workbox-config.js를 약간 수정하고 다음 부분을 추가해야 합니다.

module.exports = {
  globDirectory: "dist/",
  globPatterns: [
    "**/*.{css,ico,html,js}"
  ],
  swDest: "dist/sw.js",
  runtimeCaching: [{
    urlPattern: /\.(?:png|jpg|jpeg|svg|json)$/,
    handler: 'StaleWhileRevalidate'
  }]
};

알림: 구성 파일을 변경할 때마다 서비스 워커를 다시 빌드하는 것을 잊지 마십시오.

여기서 일어난 일을 빠르게 검토해 보겠습니다.

runtimeCaching: [{}]

먼저 서비스 워커에 런타임 캐싱 기능을 추가하는 새 속성을 추가했습니다.

urlPattern: /\.(?:png|jpg|jpeg|svg|json)$/

그런 다음 런타임에 캐시할 파일을 지정했습니다. 이 경우 모든 이미지와 JSON 응답.

handler: 'StaleWhileRevalidate'

이 부분은 캐시 작동 방식을 Workbox에 알려주기 때문에 매우 중요합니다. 여러 캐싱 전략이 있으며 가장 인기 있는 것은 Cache First(자산이 캐시에 있는 경우 거기에서 네트워크를 제공하고 네트워크를 생략하는 경우), Network First(오프라인 또는 네트워크가 실패하는 동안에만 캐시에서 제공) 및 Stale While Revalidate입니다. 여기.

이 전략이 어떻게 작동하는지 아래 다이어그램이 가장 잘 보여줍니다.

stale-while-revalidate

요청이 페이지에서 서비스 워커로 이동합니다. 응답이 캐시에 있으면 거기에서 제공되지만 동시에 네트워크 요청이 이루어지고 캐시가 최신 응답으로 업데이트됩니다. 자산이 캐시에 없으면 네트워크에서 다운로드되어 캐시됩니다.

Stale While Revalidate 전략을 사용하면 사용자가 응답을 최신 상태로 유지하면서 캐시에서 즉시 제공되는 응답의 이점을 누릴 수 있습니다.

요약

브라우저 캐시에는 여러 가지 면이 있습니다. 하나는 가장 까다로운 요구 사항을 충족하는 데 사용할 수 있는 반면 다른 하나는 제어할 수 없으며 우리가 할 수 있는 유일한 것은 약간의 조정입니다. 다음 프로젝트에서 무엇을 최대한 활용하기로 결정하든 상관없이 작동 방식을 아는 것은 성능이 우수한 웹 애플리케이션을 구축하는 데 중요합니다.

댓글남기기