Skip to main content Link Menu Expand (external link) Document Search Copy Copied

0. 캐시

캐시란? 데이터나 값을 미리 복사해 놓는 임시 장소나 그런 동작. 한 번 가져온 데이터를 가까운 곳에 저장해 두고 다음번에 다시 먼 곳에서 그것을 가져올 필요 없이 저장해둔 것을 사용하는 일종의 성능 향상 기법.

캐싱은 여러 분야에서 사용된다. 예를 들어, 하드웨어 수준에서는 먼 곳에 있는 메인 메모리 대신 가까운 곳에 있는 캐시 메모리에 자주 사용되는 데이터를 저장해둬서 CPU가 데이터를 빠르게 가져올 수 있도록 하는데, 이것도 캐싱의 한 예이다. 또한 일상생활에서도 쉽게 발견할 수 있다. 자주 사용하는 물건을 먼 곳에 있는 선반에 두지 않고 책상 바로 위에 올려두는 것도 캐싱의 한 예이다. 또는 자주 사용하는 프로그램의 아이콘을 바탕화면에 두는 것도 마찬가지이다. 이처럼 캐싱은 정말 다양한 곳에서 활용되고 있다.

1. http cache 사용하기

http 응답을 저장해 두는 저장소는 크게 두 가지로 분류할 수 있다.

사설 캐시(Private Cache)

지역 캐시(Local Cache)라고도 부른다. 이는 한 사용자에 의해서만 재활용될 수 있는 것들이 저장되는 저장소이다. 최초의 HTTP 요청은 서버에게 전송되어 HTTP 응답을 받아오게 되고, 이를 사설 캐시에 저장해 두면 다음번에 동일한 HTTP 요청이 시도될 때는 서버에 해당 HTTP 요청을 다시 보내지 않고 저장되어 있는 HTTP 응답을 재활용하게 된다.

대표적인 사설 캐시로는 브라우저 캐시가 있다. 브라우저 캐시는 기본적으로 사용자가 HTTP 요청을 통해 다운로드한 모든 문서들을 저장하고 있다. 이렇게 저장된 문서들은 뒤로 가기 혹은 앞으로 가기를 할 때, 문서 저장을 할 때, 페이지 소스 보기를 할 때 등의 경우에 재활용이 된다.

웹 브라우저가 사용하는 캐시 방식은 크게 2가지가 있다.

메모리 캐시

RAM에 데이터를 저장해두는 방식

디스크 캐시

file로 데이터를 저장해두고 그 file을 읽어서 캐시 데이터를 읽어오는 방식

이 두 가지는 우리가 직접 설정할 수 있는 옵션은 아니고 사용 빈도나 사이즈에 따라 브라우저 자체 알고리즘을 통해 알아서 처리된다.

공유 캐시(Shared Cache)가 있다.

이는 여러 사용자들에 의해 재활용될 수 있는 것들이 저장되는 저장소이다.

최초의 HTTP 요청은 공유 캐시를 거쳐 서버에게 전송되어 HTTP 응답을 받아오게 되고, 이를 공유 캐시에 저장해 두면 다음번에 동일한 HTTP 요청이 공유 캐시에게 전달될 때 서버에 해당 HTTP 요청을 다시 보내지 않고 저장되어 있는 HTTP 응답을 클라이언트에게 반환하게 된다.

2. http 캐시 엔트리(= 캐싱하는 대상)

http 캐시에 저장되는 데이터 뭉치 하나하나를 캐시 엔트리라고 부른다.

각 캐시 엔트리를 구분하는 기준은 캐시 키이다. 기본적인 캐시 키는 http요청의 메소드와 URI의 조합으로 결정된다.(일반적으로 GET 요청에 대해서만 캐싱하므로 URI로만 결정되는 경우도 있음). 즉, 메소드와 URI가 동일한 하나의 http요청은 하나의 캐시 엔트리에 대응한다.

  1. html문서, 이미지, 파일 등의 리소스를 포함하는 get요청에 대한 200 응답
  2. 301(Moved Permanetly) 응답
  3. 404(Not Found) 응답
  4. 206(Partial Content) 응답
  5. (캐시 키로 사용하기에 적절한 무언가가 정의된 경우) get이 아닌 http요청에 대한 http응답

이때, 하나의 캐시 엔트리가 여러 개의 http 응답들로 구성되어 있을 수도 있다. 이 경우 해당 http 응답들은 그 캐시 엔트리 내에서 두 번재(Secondary) 키에 의해 구분된다. 보통 그 캐시 엔트리에 대응하는 http 요청이 컨텐츠 협상(Content Negotiation)의 타겟인 경우에 해당한다. 좀 더 자세한 내용은 vary헤더에 대해 읽어보자.

3. Cache-Control 헤더

HTTP/1.1Cache-Control 헤더에는 HTTP 요청/응답에서의 캐싱 메커니즘을 결정하는 여러 디렉티브(Directive)들을 나열할 수 있다. 각각의 디렉티브는 캐싱을 어떻게 할 것인지와 관련된 일종의 옵션이라고 보면 된다. 이때 Cache-Control 헤더는 HTTP 요청 헤더와 HTTP 응답 헤더모두 사용할 수 있지만, 각각에 나열하는 디렉티브들은 서로 다른 의미를 지니며 나열할 수 있는 디렉티브들의 종류도 조금씩 다르다.

따라서 HTTP 요청의 Cache-Control 헤더에 나열된 디렉티브들이 반드시 HTTP 응답의 Cache-Control 헤더에 나열되리라는 보장은 없다. 디렉티브 이름은 대소문자를 구별하지 않으며, 여러 디렉티브들을 나열하는 경우에는 콤마로 구분한다.

HTTP 요청의 Cache-Control 헤더에 나열할 수 있는 디렉티브

max-age=

해당 HTTP 응답이 유효하다고 판단될 수 있는 최대 시간을 나타낸다. 명시된 시간보다 나이를 많이 먹은 HTTP 응답은 받아들이지 않겠다는 것을 나타낸다. 그리고 max-stale 디렉티브가 존재하지 않는다면 만료된 HTTP 응답도 받아들이지 않는다.

캐시에 남아 있는 기존 HTTP 응답을 삭제하려면 max-age=0 디렉티브를 명시하면 된다. 이는 서버에게 검증(Validation) 요청을 보내도록 강제할 것이기 때문이다.

max-stale[=seconds]

만료된 HTTP 응답을 받아들이겠다는 것을 나타낸다. 만약 이 디렉티브에 시간이 명시된다면 만료 이후의 초과 시간이 그 시간보다 크지 않은 HTTP 응답을 받아들이겠다는 것이고, 시간이 명시되지 않는다면 만료된 HTTP 응답을 무조건 받아들이겠다는 것이다.

min-fresh=seconds

만료될 때까지 명시된 시간 이상의 시간이 남은 HTTP 응답을 받아들이겠다는 것을 나타낸다. 즉, 최소한 명시된 시간 만큼은 유효할 HTTP 응답을 받아들이겠다는 것이다.

no-cache

저장되어 있는 HTTP 응답을 사용하기 전에(캐시를 사용하기 전에) 반드시 서버에게 검증(Validation) 요청을 보내야 한다는 것을 나타낸다.

no-store

해당 HTTP 요청을 통해 받아오는 HTTP 응답을 어떠한 종류의 캐시에도 저장하면 안 된다. 절대 캐시를 해서는 안 되는 리소스일 때 사용한다. 캐시를 만들어서 저장조차 하지 말라는 강력한 표시이다. 브라우저는 어떤 경우에도 캐시 저장소에 해당 리소스를 저장하지 않는다.

이때 주의해야 할 것은, 이미 캐시에 해당 HTTP 요청에 대한 HTTP 응답이 저장되어 있다면 그것은 평소와 같이 재활용될 수 있다는 것이다. 즉, 이 디렉티브는 새로운 HTTP 응답을 저장할 것이냐의 문제인 것이다.

HTTP 응답의 Cache-Control 헤더에 나열할 수 있는 디렉티브

no-cache

저장되어 있는 HTTP 응답을 사용하기 전에(캐시를 사용하기 전에) 반드시 서버에게 검증(Validation) 요청을 보내야 한다는 것을 나타낸다. max-age=0과 동일한 뜻이다. 이는 설령 만료된 HTTP 응답을 받아들이도록 설정이 되어 있는 캐시라고 할지라도 마찬가지로 적용된다.

no-store

해당 HTTP 요청을 통해 받아오는 HTTP 응답을 어떠한 종류의 캐시에도 저장하면 안 된다. 절대 캐시를 해서는 안 되는 리소스일 때 사용한다. 캐시를 만들어서 저장조차 하지 말라는 강력한 표시이다. 브라우저는 어떤 경우에도 캐시 저장소에 해당 리소스를 저장하지 않는다.

public

해당 HTTP 응답이 어떠한 캐시(모든 사람, 중간 서버)에도 저장될 수 있다는 것을 나타낸다. 이는 보통의 경우라면 캐시가 되지 않는 유형의 HTTP 응답까지도 캐싱될 수 있도록 한다.

private

해당 HTTP 응답이 사설 캐시(브라우저 환경)에만 저장될 수 있다는 것을 나타낸다. 이는 보통의 경우라면 캐시가 되지 않는 유형의 HTTP 응답까지도 캐싱될 수 있도록 한다.

must-revalidate

해당 HTTP 응답이 만료되었다면 반드시 서버에게 검증(Validation) 요청을 보내야 한다는 것을 나타낸다. 이는 설령 만료된 HTTP 응답을 받아들이도록 설정이 되어 있는 캐시라고 할지라도 마찬가지로 적용된다.

proxy-revalidate

must-revalidate 디렉티브와 완전히 동일한 의미를 갖는다. 단, 공유 캐시에만 적용되기 때문에 사설 캐시에서는 무시된다.

max-age=seconds

해당 HTTP 응답이 유효하다고 판단될 수 있는 최대 시간을 나타낸다. 해당 시간보다 나이를 많이 먹고 나면 만료된 HTTP 응답으로 판단된다. 그 시간은 요청 시각에 상대적이며, Expires 헤더가 설정되어 있어도 그것보다 우선시 된다.

s-maxage=seconds

해당 HTTP 응답이 유효하다고 판단될 수 있는 최대 시간을 나타낸다. max-age 디렉티브나 Expires 헤더가 설정되어 있어도 그것들보다 우선시 된다. 단, 공유 캐시에만 적용되기 때문에 사설 캐시에서는 무시된다. 이 디렉티브를 사용하면 암시적으로 proxy-revalidate 디렉티브도 사용이 된다.

4. 유효성 검증

캐시에 저장되는 각 http 응답은 수명(유효 기간)을 가지고 있다. 각 http 응답의 수명은 다음과 같은 순서의 로직에 의해 결정된다. 기본적으로 1, 2번이 공통이며, 3번은 브라우저마다 알고리즘이 다르다.

  1. Cache-Control 헤더의 max-age=N 디렉티브가 존재한다면, 수명은 N과 같다.
  2. Expires 헤더가 존재한다면, 수명은 Expires 헤더의 값에서 Date 헤더의 값을 뺀 것과 같다.
  3. Last-Modified 헤더가 존재한다면, 수명은 Date 헤더의 값에서 Last-Modified 헤더의 값을 뺀 것을 10으로 나눈 것과 같다. (휴리스틱 알고리즘)

만약 수명이 아직 다하지 않았다면 해당 HTTP 응답은 유효하다(Fresh)고 표현하며, 수명이 다했다면 만료되었다(Stale)고 표현한다. 기본적으로 캐시는 만료된 자원에 대한 HTTP 요청을 받으면 그 자원이 여전히 유효한지 검증하기 위해 서버에게 해당 HTTP 요청을 전달한다. 이러한 과정을 검증(Validation)이라고 한다. 이때 검증하고자 하는 HTTP 응답의 헤더 구성에 따라 검증 요청 시 서버에 전달하는 정보의 종류가 조금 달라진다. 다음 그림은 검증 요청이 일어나기까지의 과정을 보여준다.

검증 방식은 크게 두 가지이다. 먼저, 검증하고자 하는 http 응답에 Etag헤더가 존재한다면 Etag헤더의 값을 If-None-Match헤더에 포함시켜서 서버에게 검증 요청을 보낸다. 다음으로, ETag 헤더가 존재하지 않는다면 Last-Modified 헤더를 활용한다. 즉, Last-Modified 헤더의 값을 If-Modified-Since 헤더에 포함시켜서 서버에게 검증 요청을 보낸다.

이제 서버는 전달받은 Etag값 혹은 Last-Modified값을 이용하여 자원의 유효성을 검증한다. 여전히 해당 자원이 유효하다면 서버는 Body가 없는 가벼운 304(Not Modified)응답을 반환하고, 더 이상 유효하지 않다면 새로운 자원의 내용을 Body에 담아서 200응답을 반환한다. 전자의 경우에는 기존에 캐싱되어 있던 자원을 다시 사용하게 하는 것이므로 나이(Age)를 0으로 초기화시키게 되고, 필요한 경우에는 만료 시각을 갱신해줄 수도 있다.

그 과정은 아래 그림과 같다.

ETags

ETag 응답 헤더는 강함 검증으로써 사용될 수 있는 User-Agent에게 있어 불투명한 값이다. 브라우저와 같은 http 사용자 에이전트가 이 문자열이 무엇을 표현하는지 알 수 없고, 그것의 값이 무엇이 될지를 예측할 수 없다는 것을 의미한다. ETag헤더가 리소스에 대한 응답의 일부라면, 클라이언트는 이후 요청의 헤더 내에 If-None-Match헤더를 포함시켜서 서버에게 검증 요청을 보낼 수 있다. 304 Not Modified 응답은 304 Not Modified 상태 코드와 함께 ETag헤더를 포함시킨다. 이때 ETag헤더의 값은 If-None-Match헤더의 값과 같다. 이러한 과정을 통해 서버는 클라이언트에게 리소스가 변경되지 않았음을 알려준다.(리소스 자체를 보내지 않고)http://www.snowtown.com/login

Last-Modified

Last-Modified 응답 헤더는 약한 검증으로써 사용될 수 있다. 그것이 1초의 해상도만 가질 수 있기에 약하다고 간주된다. Last-Modified 헤더가 응답 내에 존재하면, 클라이언트는 캐시된 문서를 검증하기 위해 If-Modified-Since 요청 헤더를 줄 수 있다.

5. CDN(Content Delivery Network)

여러 사용자들이 자원을 가져다 쓸 수 있는 일종의 공유 캐시라고 볼 수 있다. 즉, 여러 사용자들에 의해 재활용될 수 있는 자원들이 저장되는 곳이다.

CDN을 구성하는 공유 캐시 서버가 여러 지역에 분산되어 있으며, 그렇게 분산되어 있는 공유 캐시 서버들이 자원을 가져오는 실제 서버는 따로 있다.

이것이 중요한 이유는, 자원을 요청하는 사용자의 위치에 따라 더 가까운 공유 캐시 서버로부터 자원을 가져올 수 있게 하기 때문이다. 이는 GSLB(Global Server Load Balancing)라고 하는 발전된 형태의 DNS 기술에 의해 가능한 것인데, 간단히 요약하자면 자원을 요청하는 사용자와 가장 가까운 공유 캐시 서버의 IP 주소를 알려주도록 DNS 서버가 구현되어 있다는 것이다.

이러한 원리로 네트워크 트래픽을 최소화시키고 자원을 가져오는 성능을 향상하는 기술이 바로 CDN이다.

참고자료

  • https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching
  • https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
  • https://it-eldorado.tistory.com/142