Computer Science/Network

웹 캐시(web cache) 종류 및 동작 순서

둉이 2022. 8. 7. 22:54

캐시데이터에 빠르게 접근하기 위해 자주 사용되는 데이터나 값을 미리 복사해 놓는 임시 장소를 의미한다.

 

 

캐시를 적용하기 좋은 데이터는 다음과 같다.

 

- 자주 참조되는 데이터

- 자주 변경되지 않는 데이터

- 동일한 입력에 대해 동일한 출력을 보장하는 데이터

 

 

프론트엔드에서는 서버에 요청을 보내고 응답을 받아 화면을 렌더링하는 경우가 많다.

 

네트워크를 통해 서버에 데이터를 요청하게 되면, 해당 서버가 응답을 반환할 때까지 페이지가 로드되지 않는다.

 

페이지 로딩에 필요한 css, js, html, 이미지 등의 정적 리소스를 캐싱하여 사용하게 되면, 요청을 보내는 네트워크 요청 횟수를 줄일 수 있을 뿐만 아니라 서버 응답을 기다려야 할 필요도 없기 때문에 사용자에게 보다 빠르게 화면을 보여줄 수 있다.

 

이러한 개념을 웹 캐싱이라고 한다.

 

 

 

웹 캐시의 종류에 대해 알아보자.

 

일반적으로 브라우저에서 리소스를 요청하게 되면, 다음과 같은 순서로 캐시가 동작한다.

캐시 동작 순서

 

브라우저 단에서 HTTP 요청을 보내게 되면, 해당 요청을 바로 서버로 전송하지 않고 캐시된 데이터가 있는지 확인한다.

 

캐시는 차례대로 서비스 워커 캐시 → 브라우저 캐시 → CDN(or 프록시 서버 캐시) 순으로 참조되며, 모든 캐시에 유효한 응답(데이터)이 없을 경우에는 서버에 요청을 보내서 응답을 받게 된다.

 

 

 

서비스 워커 캐시

서비스 워커란 웹 워커의 일종으로, 백그라운드에서 비동기적으로 실행되는 자바스크립트이다.

 

웹 브라우저는 싱글 스레드를 사용하기 때문에 동시에 여러 자바스크립트 코드를 실행하는 것은 불가능하다.

 

특히, 응답 시간이 오래 걸리는 코드를 실행할 경우에는 해당 코드의 실행이 완료될 때까지 메인 스레드를 사용할 수 없게 되므로 서비스 운영에 문제가 생길 수 있다.

 

이러한 경우에는 서비스 워커를 사용하여 독립된 스레드에서 자바스크립트 코드를 실행할 수 있다.

 

하지만 서비스 워커는 보안 상의 이유로 https에서만 사용할 수 있으며, 서비스 워커 내에서는 동기적 XHR이나 웹 스토리지 접근이 불가능하므로 주의하자.

 

 

서비스 워커는 캐시 스토리지 API를 제공한다.

 

캐시 스토리지 API를 사용하여 서비스 워커 내에서 캐싱을 하는 경우에는 브라우저 캐시를 참조하기 전에 서비스 워커에서 HTTP 요청을 가로채고 캐싱 전략에 따라 서비스 워커 캐시 내에 있는 데이터를 먼저 사용한다.

 

캐시 스토리지 API에 대해 자세한 정보는 아래에서 볼 수 있다.

 

Cache - Web API | MDN

Cache 인터페이스는 ServiceWorker 의 생명주기의 일부로 캐시 된 Request와 Response를 나타냅니다.

developer.mozilla.org

 

 

브라우저 캐시

HTTP 캐시라고도 하며, 브라우저 혹은 HTTP 요청을 보내는 클라이언트의 내부 디스크 혹은 메모리에 저장되는 캐시이다.

 

각 사용자마다 할당되는 개인화(personalized)된 캐시이므로 클라이언트 간 공유는 불가능하다.

 

모든 HTTP 요청은 먼저 서버로 직접 요청을 보내지 않고 브라우저 캐시로 라우팅되는데, HTTP 캐시 내에 필요한 데이터가 존재한다면 캐시된 데이터를 반환한다.

 

브라우저 캐시는 HTML의 메타 태그로 캐시 속성을 지정하거나, HTTP 요청 및 응답 헤더에 지정하는 방법으로 제어가 가능하다.

 

 

 

각각의 방법과 속성에 대해 알아보자.

 

HTML 헤더에 캐시 속성 지정

HTML 요청/응답 헤더에 Cache-Control, Expires, ETag, Last-Modified 등의 속성을 지정하여 캐싱 제어가 가능하다.

 

cache-control 속성에서 사용 가능한 디렉티브 값과 설명은 다음과 같으며, 여러 개의 디렉티브를 적용하고자 하는 경우에는 콤마(,)로 구분한다.

 

 

1) HTTP 요청에서 cache-control 속성에 사용 가능한 값

디렉티브 설명
no-cache 응답으로 받은 데이터를 캐싱하되, 매번 서버에 요청하여 해당 데이터에 대한 유효성 검사를 하도록 강제함
max-age: 0과 동일하게 동작
no-store 어떠한 데이터도 캐싱하지 않음
no-transform 캐싱할 데이터에 대해 압축이나 포맷 변환 등의 작업을 진행하지 않음
only-if-cached 캐시된 데이터가 있을 경우에만 반환
(캐시된 데이터가 없어도 서버에 요청하지 않음)
max-age 현재 시간으로부터의 캐시가 유효하다고 판단할 수 있는 상대적인 시간(= 초, second)
기간 내라면 서버를 거치지 않고 캐시된 데이터를 사용하고, 기간이 만료되었다면 서버로 요청을 보내서 유효성 검증
max-age: 86400는 하루, max-age: 31536000는 1년을 의미함
Expires 헤더의 값보다 우선시
max-stale 캐시된 데이터가 있다면 만료된 이후에도 지정한 시간만큼 만료된 데이터를 사용하는 것을 허용(= 초, second)
max-stale: 60인 경우, 만료가 되더라도 이후 1분 동안은 사용 가능
min-fresh 캐시될 데이터가 변경되지 않아야 할 최소 시간(= 초, second)
지정한 시간 내에는 서버에서 해당 데이터의 값이 변경되지 않아야 함
min-fresh: 60인 경우, 요청한 데이터가 1분 동안 변경되지 않는 것을 보장한다면 캐시된 데이터를 반환

 

2) HTTP 응답에서 cache-control 속성에 사용 가능한 값

디렉티브 설명
public CDN이나 프록시 서버 같은 공용 캐시에서도 캐싱 허용
private 브라우저 캐시 등의 로컬 캐시에서만 캐싱이 가능
must-revalidate 캐시된 데이터를 사용하기 전에 반드시 서버에 요청을 보내서 유효성 검사를 하도록 강제함
(네트워크 오류 상황에서 만료된 캐시를 사용하는 경우를 방지)
proxy-revalidate must-revalidate와 동일하나, 프록시 캐시와 같은 공유 캐시에만 적용
no-cache 위와 동일
no-store 위와 동일
no-transform 위와 동일
max-age 위와 동일
s-maxage max-age와 동일하나, 프록시 캐시와 같은 공유 캐시에만 적용

 

no-cache, no-store 등의 디렉티브는 요청 헤더와 동일하게 사용이 가능하다.

 

요청 헤더와 다른 점으로는 공유 캐시 전용 디렉티브인 proxy-revalidate와 s-maxage이 추가되었다.

 

만약 max-age: 0, s-maxage: 31536000으로 설정한다면 공유 캐시에서는 1년 동안 유효한 캐싱된 데이터를 가지지만, 브라우저에서는 캐시 데이터의 유효성 검증을 위해 매번 서버에 요청을 보내게 된다.

 

3) HTTP 응답에서 Expires 속성에 사용 가능한 값

디렉티브 설명
http-date
(timestamp)
지정한 날짜 및 시간까지만 캐싱된 데이터가 유효
그 외 캐시된 데이터가 유효하지 않음(이미 만료됨)

 

Expires 속성은 응답 데이터가 더 이상 유효하지 않다고 판단할 날짜 혹은 시간을 의미한다.

 

기간 내라면 서버를 거치지 않고 캐시된 데이터를 사용하고, 만료되었다면 해당 캐시된 데이터에 대한 유효성을 검증한다.

 

만약 Expires 속성과 함께 Cache-Control: max-age 혹은 s-maxage 값을 지정하는 경우에는 Expires 속성이 무시된다.

 

Expires: Wed, 21 Oct 2015 07:28:00 GMT
Expires: 0

 

위와 같이 timestamp 형태의 절대적인 시간 값을 넘겨줄 수 있으며, 해당 날짜 및 시간까지 데이터가 유효함을 의미한다.

 

날짜가 아닌 숫자 데이터를 넘기는 경우에는 해당 데이터가 유효하지 않음을 의미한다.

 

 

4) HTTP 응답에서 ETag 사용

ETag를 사용한 캐시 유효성 검증

 

ETag란 리소스가 업데이트 되었는지 확인하는 데 사용되는 값으로, 리소스의 내용에 변경 사항이 있다면 ETag의 값도 달라진다.

 

ETag의 값을 대조하여 캐시된 데이터의 유효성을 확인할 수 있다.

 

Etag 검증에 사용되는 태그로는 If-None-MatchIf-Match가 있다.

 

HTTP 요청 시, If-None-Match 속성의 값으로 이전 응답에서 받은 ETag를 함께 전달하면 서버에서는 현재 리소스의 ETag 값과 비교한다.

 

만약 값이 동일하다면 304 Not Modified 응답을 반환하고, 리소스가 변경되었다면 서버에서는 새로운 ETag를 헤더에 담은 후 200 OK 응답을 반환한다.

 

브라우저에서는 304 응답을 받은 경우에는 캐시된 데이터를 사용하게 되고, 200 응답을 받은 경우에는 서버에 새로운 데이터를 요청한 후 ETag 값을 갱신한다.

 

5) HTTP 응답에서 Last-Modified 사용

Last-Modified는 데이터가 마지막으로 수정된 시간을 의미한다.

 

HTTP 요청 시, 브라우저는 이전 응답에서 받은 Last-Modified를 If-Modified-Since 속성의 값으로 함께 전달한다.

 

서버는 데이터의 최종 수정 시간을 If-Modified-Since의 값과 비교하고, 만약 값이 동일하다면 304 Not Modified 응답을 반환하고, 리소스가 변경되었다면 서버에서는 새로운 Last-Modified를 헤더에 담은 후 200 OK 응답을 반환한다.

 

브라우저에서는 304 응답을 받은 경우에는 캐시된 데이터를 사용하게 되고, 200 응답을 받은 경우에는 서버에 새로운 데이터를 요청한 후 Last-Modified 값을 갱신한다.

 

 

HTML의 메타 태그로 캐시 속성 지정

<!-- Expires 속성 사용: 지정한 날짜까지만 캐시 유효  -->
<meta http-equiv="Expires" content="Sun, 07 Aug 2022 18:51:54 GMT">

<!-- Expires 속성 사용: 즉시 캐시 만료 -->
<meta http-equiv="Expires" content="0">

<!-- Cache-control 속성 사용: 캐시를 하지 않음 -->
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate"/>

<!-- Pragma 속성 사용: 캐시를 사용하되 서버로 요청을 보내 유효성 검증 강제(Cache-Control: no-cache와 동일) -->
<meta http-equiv="Pragma" content="no-cache"/>

 

HTML의 메타 태그를 사용하여 Expires, Cache-Control 등의 속성을 지정할 수 있다.

 

Pragma 속성의 경우에는 Cache-Control과 동일하게 동작하지만, HTTP 1.0에서만 사용이 가능하므로 되도록 사용을 지양하는 것이 좋다.

 

 

유효하지 않은 캐시 데이터 무시

웹 캐시는 한 번 요청하여 캐싱된 리소스는 만료될 때까지 사용된다.

 

파일명이 동일하다면 파일을 수정하고 재배포를 했음에도 불구하고 브라우저에서는 변경 여부를 알아채지 못하고 캐시된 이전 파일을 가져오는 문제점이 발생할 수 있다.

 

이를 해결하기 위해서는 리소스 요청 시 버전 식별자를 사용하여 새로운 리소스가 있음을 브라우저에게 알려주는 방법이 있는데, 이를 캐시 버스팅(cache busting)이라고 한다.

 

 

 

캐시 버스팅 방식에는 다음과 같은 종류가 있다.

 

1) 파일 이름에 식별자 추가

/* 버전 사용 */
<script type="text/javascript" src="./fileName.v1.js"></script>

/* timestamp 사용 */
<script type="text/javascript" src="./fileName.20220805155529.js"></script>

파일명에 버전 혹은 timestamp 값을 넣어주거나, 버전 별로 파일 경로를 나누는 방법으로 캐시 버스팅이 가능하다.

 

2) 파일 경로에 식별자 추가

/* 버전 사용 */
<script type="text/javascript" src="v1/fileName.js"></script>

/* timestamp 사용 */
<script type="text/javascript" src="20220805155529/fileName.js"></script>

버전 별로 폴더를 생성하여 리소스를 관리하는 방식으로 캐시 버스팅이 가능하다.

 

3) 쿼리 스트링을 사용하여 식별자 추가

/* 버전 사용 */
<script type="text/javascript" src="./fileName.js?v=1"></script>

/* timestamp 사용 */
<script type="text/javascript" src="./fileName.js?timestamp=20220805155529"></script>

파일명 뒤에 쿼리 스트링으로 버전 혹은 timestamp 값을 전달하여 캐시 버스팅이 가능하다.

 

예전에는 일부 cdn과 프록시 서버의 설정에 따라 쿼리 스트링을 사용하여 http 요청을 보내는 경우에는 캐시를 참조하지 않는 문제가 발생하기도 했다.

 

최근에는 많은 CDN, 프록시 서버 제공사에서 기본 옵션으로 쿼리 스트링을 사용할 경우에도 캐싱이 동작하도록 설정을 제공한다고 하니 안심하고 사용해도 될 것 같다.

 

4) 웹팩 등의 번들러 설정을 이용한 파일 이름 해싱

웹팩을 사용하는 경우에는 아래와 같이 webpack.config.js 설정 파일에 코드를 작성하여 파일명에 해시를 추가할 수 있다.

// webpack.config.js
module.exports = {
  output: {
    // 매 빌드 시마다 해시값을 생성하여 모든 파일에 공통된 해시값 부여
    filename: '[name].[hash].js',
    // 각 파일의 내용을 기준으로 서로 다른 해시값을 생성
    filename: '[name].[chunkhash].js',
    // 변경된 파일만 이전과 다른 해시값을 생성
    filename: '[name].[contenthash].js',
  }
};

 

 

 

프록시 캐시

클라이언트와 서버 사이에 위치한 네트워크 상(프록시 서버)에서 동작하는 캐시로, 회사나 IPS의 방화벽에 설치된다.

 

브라우저 캐시와는 달리 다수의 웹 서버에서 공유하여 사용할 수 있다는 장점이 있다.

 

프록시 캐시와 비슷한 개념으로는 CDN이 있다.

 

CDN(Content Delivery Network)이란?

콘텐츠를 효율적으로 전송하기 위해 지리적으로 분산된 서버 네트워크
실 서버에 접속하여 리소스를 다운로드하는 것이 아닌 클라이언트와 가장 가까운 곳에 위치한 네트워크에 요청하여 리소스를 받아옴

→ CDN을 적절하게 사용하면 데이터 사용량이 많은 어플리케이션의 페이지 로딩 속도를 개선할 수 있음

 

CDN은 하나의 커다란 공유 캐시와 같다. 다수의 실 서버가 갖고 있는 데이터를 캐시하여 클라이언트에게 제공한다.

 

 

 

참고 자료

 

웹팩(Webpack) 캐시 제어

웹팩(Webpack) 컴파일로 생성 된 파일에서 변경된 내용이 없다면 브라우저는 캐시 상태를 유지하고 그대로 사용하게된다. 브라우저 캐시의 변경된 내용을 어떻게 확인 하는 것일까.

jinminkim-50502.medium.com

 

Caching Resources With Query Strings – Biz Coder

This afternoon Scott Hanselman posted a fairly innocuous question on Twitter.  However, the question involved the versioning of a RESTful API, which is a subject that is sure to bring out lots of opinions.  This post is less about the versioning questi

www.bizcoder.com

 

Cache-Control - HTTP | MDN

Cache-Control 일반 헤더 필드는 요청과 응답 내의 캐싱 메커니즘을 위한 디렉티브를 정하기 위해 사용됩니다. 캐싱 디렉티브는 단방향성이며, 이는 요청 내에 주어진 디렉티브가 응답 내에 주어진

developer.mozilla.org

 

웹 서비스 캐시 똑똑하게 다루기

웹 성능을 위해 꼭 필요한 캐시, 제대로 설정하기 쉽지 않습니다. 토스 프론트엔드 챕터에서 올바르게 캐시를 설정하기 위한 노하우를 공유합니다.

toss.tech

+ (special thanks to 상훈 은비 민하 나희)