프로그래밍/Network

[네트워크] HTTP 캐시(Cache) 정책에 대해서 - 최적의 캐시 전략을 만들자

Jay Tech 2019. 9. 26. 15:15
반응형

다음 내용은 Google의 Web에 관한 칼럼을 번역한 내용이다. 출처는 글 하단에 명시하였다. 최대한 직역을 하려고 했으며 직역이 어색한 경우 괄호안에 옮긴이 표시로 부연설명을 해놓았다.

 

개요

네트워크 상에서 무언가를 가져오는 것은 느리고 비용이 크다. 크기가 큰 응답 들은 클라이언트와 서버 사이에 많은 왕복작업이 드는데 그것은 자원이 필요할 때나 브라우저가 작업을 할 때 지연을 시키고 방문자에게 데이터 비용을 초래한다. 결과적으로 캐시 능력과 이전 자원을 재사용하는 것은 성능 최적화에 있어서 매우 중요한 부분이다.

 

좋은 소식은 모든 브라우저는 HTTP 캐시를 구현하고 있다는 점이다. 당신이 해야할 일은 각 서버단에서 맞는 HTTP 헤더를 내려주어 브라우저에게 응답 캐시를 언제 얼마나 보유할지 가이드하기만 하면 된다.

 

Cache를 내려주는 모습

서버가 응답을 내려 줄 때, HTTP 헤더들을 방출하고 그것은 content-type, 길이, 캐시전략, 유효성 토큰 등 여러 정보들을 내려준다. 예를들어, 위 사진과 같이 서버는 1024 바이트의 응답을 내려주고 클라이언트에게 120초 동안 유지하라고 시키며 유효 토큰 ("x234dff") 을 제공한다. 유효토큰은 응답 이후 자원이 수정되는 유효기간을 체크한다.

ETags로 캐시 검증하기

- 서버는 ETag를 HTTP 헤더에 담아 유효 토큰으로 통신한다.

- 유효토큰은 효율적인 자원 업데이트 체크를 가능하게 한다. 리소스가 바뀌지 않는다면 아무 데이터도 전달되지 않는다.

 

초기 요청 이후 120초가 지났다고 가정하고 브라우저가 같은 자원에 대해 새로운 요청을 보냈다고 해보자. 첫째로, 브라우저는 로컬 캐시를 확인하고 이전의 응답을 찾아본다. 불행히도, 브라우저는 응답 시간이 만료되었으므로 이전 응답을 찾을 수가 없다. 이 시점에서, 브라우저는 새로운 요청을 보내고 새로운 전체적인 응답을 기대한다. 그런데 이것은 매우 비효율적인데, 서버측 자원이 바뀌지 않았다면 이미 캐시에 있던 자원을 또 다운로드하기 때문이다! (만료기간이 지났다고 표기되었으므로)

 

그래서 Etag 헤더를 정의해 문제를 해결할 수 있다. 서버는 중재 토큰을 생성하는데 그것은 전형적으로 파일에 대한 해시값 또는 파일 내용에 대한 지문(식별자)이다. 클라이언트는 이 식별자가 어떻게 만들어졌는지 알 필요가 없다. 그것은 서버에게 다음 번 요청 때 보내기만 하면 된다. 식별자가 같다면, 리소스가 바뀌지 않았다는 것이므로 다운로드를 건너 뛸 수 있다.

ETag 의 설정

이전의 예에서, 클라이언트는 Etag 토큰을 Http request 헤더의 "If-None-Match"에 담아서 보낸다. 서버는 토큰을 현재 자원과 비교한다. 토큰이 바뀌지 않았다면 서버는 "304 Not Modified"를 리턴하는데 그것은 브라우저에게 너가 이미 가지고 있는 캐시에서 (만료는 되었지만) 바뀐것이 없고 다시 120초의 시간을 재할당하라고 알려준다. 다운로드를 다시 할 필요가 없으니 시간과 대역폭을 절약할 수 있다는 것이다.

 

웹 개발자로써, 효과적인 재검증을 어떻게 할 수 있을까? 브라우저는 우리를 대신하여 이런일들을 해준다. 브라우저는 자동으로 유효토큰이 이미 정의되었었는지를 검사해주고 다시 보낼 요청에 붙여주고, 서버의 응답에 따라 캐시의 타임스탬프를 업데이트 시켜준다. 오직 할일은 서버에서 필요한 ETag 토큰들을 내려주는 것이다. 필요한 설정 플래그들을 확인하기 위해 당신의 서버 문서를 확인해라.

Cache-Control

- 각 자원은 HTTP 헤더의 Cache-Control 을 통해서 정의된다.

- Cache-Control 속성은 누가 응답을 어떤 조건에서, 얼마나 캐시할 수 있는지 정의한다.

 

성능 최적화의 관점에서, 가장 좋은 요청은 서버와 통신하지 않는 요청이다. 로컬에 복사되어 있는 응답(=캐시)은 네트워크 레이턴시를 줄여주고 데이터 통신에 드는 데이터 비용들을 없애준다. 이런것들을 하기 위해서는 HTTP 명세에서 서버가 Cache-Control 속성을 통해 어떻게 얼마나 응답을 캐시해야하는지 리턴해주게 하면 된다.

 

참고) Cache-Control 속성은 HTTP/1.1 스펙에서 정의되었고 응답 전략에 해당하는 기존 헤더들 (예를들어, Expires) 을 대체한다. 최근 브라우저들은 Cache-Control을 지원한다.

 

Cache-Control

no-cache, no-store

"no-cache"는 반환된 응답이 자원이 바뀌었다면 서버의 첫 번째 응답체크를 하지 않고서야 이어지는 요청을 만족시킬 수 없다는 것을 나타낸다.(직역하면 이렇지만 이해하기 어렵다. 이 말은 요청할 때마다 매번 ETag를 검사한다는 것이다. max-age가 0 라는 의미와 같다. 매번 요청을 통해 ETag를 검사하기 때문에 자원이 바뀌었다면 바로 다운로드가 가능하고 바뀌지 않았다면 다운로드를 피할 수 있다) 결과적으로, 적절한 유효 토큰(ETag)이 존재한다면, no-cache는 캐시 응답을 체크하기 위해 왕복 통신이 발생하게 된다. 하지만 자원이 바뀌지 않았을 때 다운로드는 피할 수 있다.

(이해를 돕기 위해 부연 설명을 하자면, 캐시가 있다면 요청자체를 하지 않지만 no-cache로 한다면 매번 요청을 진행하여 ETag를 검사한다.)

 

반대로, "no-store"는 훨씬 간단하다. 반환된 응답의 어떤 버젼이라도 브라우저에게 캐시를 하지 말라는 설정이다. 예를들어, 은행 데이터의 private 한 개인 정보를 포함한다고 생각해보자. 매번 유저가 이 자원을 요청할 때, 요청은 서버로 보내지고 전체 응답이 다운로드 된다. (재활용 하면 안되므로)

public vs private

응답이 public 으로 되어있다면 응답 코드가 정상적으로 캐시할 수 있다는 코드가 아니더라도, HTTP 검증과 연관되어 있더라도 어찌됐건 캐시를 할 수 있다는 의미이다. 대부분의 시간동안 "public"은 필요가 없다. 왜냐하면 max-age 와 같은 설정을 통해 명시적으로 캐싱할 수 있었고 응답이 어쨌든 캐시될 수 있다는 의미이기 때문이다.

 

반대로, 브라우저는 "private" 응답을 캐시는 할 수 있지만 응답은 전형적으로 싱글 유저(=말단 유저: 옮긴이)를 타겟으로 하고 중간 매개체들은 캐시할 수 없다는 의미이다. 예를들어, 유저의 브라우저는 개인 정보가 있는 HTML 페이지를 캐시할 수 있지만 CDN은 페이지를 캐시할 수 없다는 말이다. (말단 사용자만이 캐시할 수 있고 CDN 같은 중간 매개체들은 캐시할 수 없다라고 이해하면 된다: 옮긴이)

 

Cache 계층 구조

최적의 Cache-Control 전략 세우기

재사용 가능한 응답인가?

(X) → no-store

매번 검증을 해야 하는가?

(O) → no-cache

중간 매개체가 캐시 해도 되는가?

(X) → private

(O) → public

캐시의 유효기간 설정 → max age → ETag 헤더 추가 등등

 

결정 트리를 따라가면서 당신의 애플리케이션에서 사용하는 특정 리소스들에 대한 최적의 캐시 전략을 세워보자. 이상적으로는 클라이언트 측에 많은 응답들에 대해 최대한 오래 캐시를 하는 것을 목적으로 하고 효율적인 재 검증을 위해 유효토큰을 설정하자.

Cache-Control directives 예시

max-age=86400

응답을 하루(1 day) 로 캐시 하도록 설정하기. (60초 * 60분 * 24시간)

private, max-age=600

응답을 클라이언트 브라우저 (말단) 에 10분으로 설정하기.

no-store

응답은 캐시를 허용하지 않으며 매 요청마다 갱신한다.

 

HTTP Archive 에 따르면 30만개의 웹 사이트 가운데 (Alexa rank 에 의한) 브라우저가 통상 응답에 대한 다운로드들 중 절반을 캐시 한다고 한다. 그것은 페이지뷰와 방문의 반복에 커다란 절약이다. 물론, 그것이 당신의 웹사이트에서 자원의 50%를 캐시해야 한다는 의미는 아니다. 몇몇 사이트들은 자원의 90% 이상을 캐시하기도 하는 반면 어떤 곳들은 시간에 민감한 데이터나 개인 데이터들에 대한 것들 때문에 캐시를 전혀 하지 않는 곳도 있다.

캐시 만료기간 전 자원이 업데이트 된다면?

당신이 캐시 응답을 업데이트하거나 비활성화하려면 어떻게 해야할까?

예를들어, 당신의 방문자들에게 (각 브라우저) CSS 스타일 시트를 24시간 캐시 하라고 했다고 가정하자. 그런데 디자이너가 방금 막 업데이트를 커밋하고 모든 사용자들에게 주고 싶어 한다. 그렇다면 당신은 어떻게 모든 이전의 CSS 자원을 가진 사용자들에게 어떤 것이 소위 말해 "stale(오래된, 신선하지 않은)" 한 자원을 업데이트 시킬 수 있을까? 최소한 자원의 URL을 변경하지 않는 이상 이 일은 할 수 없다.

 

브라우저가 응답을 캐시한 이후 캐시기간이 만료하기 전까지(max-age나 expire 설정에따른) 혹은 어떤 이유에서 캐시에서 삭제되거나 할 때까지 사용된다. 결과적으로 다양한 사용자들은 완성단계에 이르기 전까지 다른 버젼의 리소스들을 사용하게 된다. 어떤 사용자들은 최신 버전의 자원을 사용하고 어떤 사람들은 이전에 캐시된 상대적으로 오래된 자원을 사용한다.

 

어떻게 클라이언트 캐시와 서버 단 빠른 업데이트 둘 다를 만족시킬 수 있을까? 리소스의 URL을 바꾸고 컨텐츠가 바뀌었다면 새로 다운로드 받게 강제할 수 있다. 전형적으로, 파일의 식별자 (혹은 version number) 를 파일 이름에 삽입한다. 예를들어 sytle.x234dff.css 처럼 말이다.

캐시 전략 예시

 

리소스 당 캐시 전략을 정의하는 것은 "cache hierarchies" 캐시 계층구조를 가능하게 해주는데 각각이 얼마동안 캐시가 되는지 설정할 수 있을 뿐만 아니라 방문자들로 하여금 새로운 버젼을 얼마나 빠르게 볼 수 있는지 정의할 수 있게 해준다.

 

HTML 은 "no-cache"로 정의를 한다. 그것은 브라우저는 항상 재검증을 하게 하고 최신 버전의 자원을 각 요청마다 받아올 수 있게 하는 것을 의미한다. 또한 HTML 코드 상에 CSS와 JavaScript 자원에 대한 식별자를 임베드 한다. 서버 측에서 자원이 변경이 되면 HTML 도 새로운 자원이 다운로드 되기 때문에 바뀌게 된다.

 

CSS 는 브라우저 들과 중간 캐시 들 (예를들어, CDN) 까지 1년 동안 캐시할 수 있게 허용한다. 주목할 것은 당신은 1년간의 안전한 "far future expires" 전략을 사용할 수 있게 된다. 왜냐하면 파일의 식별자를 임베드 해놓았기 때문이다. CSS 가 업데이트 되면 URL 도 바뀌게 된다.

JavaScript도 마찬가지로 1년으로 설정이 되었지만 private 까지 붙어있다. 아마 CDN에서 캐시 하면 안될 개인 정보가 포함 되어 있을 것이다.

Image는 버전이나 고유한 식별자 없이 1년 캐시가 되어있다.

 

ETag, Cache-Control 그리고 고유한 URL들의 조합으로 long-lived 한 만료시간과 응답에 대한 캐시 컨트롤, 그리고 언제든 가능한 업데이트를 가능하게 함으로써 세상에서 가장 좋은 전달을 가능하게 한다.

Caching 체크 리스트

캐시 전략의 베스트는 없다. 트래픽 패턴과 제공하는 데이터의 타입 그리고 앱의 특성을 고려하여 자원에 대한 적절한 전략과 캐시 계층 구조를 세워야 한다.

몇 가지 팁으로 캐시 전략을 세울 수 있다.

일정한 URL을 써라

같은 자원을 다른 URL로 서비스 하고 있다면 그 자원은 여러번 요청되고 저장될 것이다.

서버 측에서 유효토큰(ETag)을 제공하게 하라

유효 토큰은 서버에서 자원이 바뀌지 않을 때 같은 바이트들을 통신하는 것을 막아준다.

어떤 자원들이 중간 저장을 가능하게 할지 결정하라

모든 사용자들에게 동일한 자원은 CDN이나 다른 중간 매개체들에게도 캐시할 수 있게 하는 좋은 후보다.

각 자원의 최적의 캐시 기간을 정해라

다양한 자원은 각기 다른 신선도를 요구한다. 살펴보고 각각의 max-age를 적절히 선택하라.

당신의 사이트에 최적의 캐시 계층구조를 결정해라

리소스 URL과 자원의 식별자 HTML 문서에 대한 short나 no-cache 전략으로 업데이트에 빠르게 대응해라.

churn(휘젓기) 을 최소화하라

(churn이라는 뜻이 휘젓기라는 뜻인데 파일의 분류를 잘 해놓으라는 뜻으로 이해하였다: 옮긴이)

어떤 리소스들은 다른 것들보다 자주 바뀐다. 만약 리소스의 특정 부분들 (예를 들어, JavaScript 함수나 CSS 스타일 파일들의 집합들) 이 자주 업데이트된다면 따로 파일들을 분류해서 내려주는 것이 낫다. 그렇게 되면 나머지 컨텐츠들에 대해 서는 (자주 업데이트 되지 않는 항목들) 업데이트가 될 때 캐시로부터 다운로드를 줄일 수 있다.

 

원문) https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching

반응형